티스토리 뷰
Before(Service -> Repository)
지하철 미션을 진행하면서 리뷰어분께 다음과 같은 피드백을 받았다.
현재 지하철 애플리케이션에는 지하철 노선과 관련된 비즈니스 로직을 담당하는 LineService가 있고, 노선의 역과 관련된 비즈니스 로직을 담당하는 StationService가 있다. 각 서비스 계층에 연결된 LineRepository, StationRepository도 함께 존재했다.
계층형 구조를 가져가면서 Service가 다른 도메인의 Repository(DAO)를 참조하는 것은 당연하다고 생각했다. 오히려 Service가 다른 Service를 의존하는 형태를 지양했다. 왜냐하면 계층형 구조에서 Service가 Service를 참조하는 구조는 순환참조가 발생할 수 있다고 생각했고, 서비스 계층의 관심사의 분리가 제대로 되지 않은 형태라고 생각했었다. 그리고 컨트롤러 -> 서비스 -> 레포지토리로 흐르는 flow를 유지하는 형태가 좋지 않을까? 라고 막연하게 생각했다.
리뷰어분께서 말씀해주신 부분은 Repository(DAO) 계층은 해당 도메인의 영속성(Entity)를 관리하기 위함이 역할인데, 본인이 아닌 다른 도메인이 해당 영속성을 관리하는 권한을 가질 수 있다는 것이었다. 즉 LineService에서 Station에 관한 정보(비즈니스 로직)를 얻기 위해 StationService가 아닌, Station의 영속성을 관리하는 StationRepository를 참조하고 있다는 것이다.
더 나아가 생각해보면 서비스가 서비스 계층을 가질 수 밖에 없는 상황이 발생할 수 밖에 없다고 생각이 들었다. 예를 들어 주문 시스템이 있다고 했을 때, 주문을 할 때 메뉴와 관련한 기능도 필요할 것이고 결제와 관련된 기능들도 필요할 것이다. 즉, 상위 서비스와 하위 서비스 같은 개념을 가져갈 상황이 생길 수 밖에 없다는 생각이 들었다.
지금까지 명확한 이유 없이 계층형 구조를 사용하는 관점에서Service가 다른 Service를 의존하면 안돼 ! 라는 생각을 가지고 있었던 것 같아서, 서비스가 서비스를 사용할 수 있는 구조로 변경해보고 싶었고 2단계를 구현한 후 바로 적용해보았다.
서비스가 다른 서비스를 사용할 수 있도록 하기 위해서는 반환타입을 도메인(or DTO)으로 변경하는 과정이 필요했고, 서비스 계층에서 비즈니스 로직을 다루고 있기 때문에 도메인을 반환하는 구조로 변경해보았다.
이 과정에서 DTO를 반환할 것인가, 도메인을 반환할 것인가 고민이 되었는데
DTO로 반환하는 형태의 장점은 계층과 계층(컨트롤러) 사이에서 값을 전달할 때 도메인이 아닌, 순수한 값을 전달할 수 있었고
DTO로 반환하는 형태의 단점은 다른 서비스에서 서비스를 호출하는 경우 DTO를 도메인으로 변환하는 작업이 필요했다.
도메인으로 반환하는 형태의 장점은 다른 서비스에서 따로 변환과정을 거칠 필요없이 도메인을 사용할 수 있었고
도메인으로 반환하는 형태의 단점은 다른 계층(컨트롤러)에서 도메인에 접근할 수 있는 가능성이 생겼다.
결론적으로 시간이 많지 않았기 때문에 변환과정을 거치지 않고 직관적인 도메인을 반환하는 구조를 가져가기로 결정했고, 생각보다? 빠르게 구조를 변경할 수 있었다.
After (Service -> Service)
리팩토링 후 변경된 구조는 아래과 같다.
LineService는 StationRepository 대신 StationService를 가지고, LineService를 StationService의 상위 개념 서비스로 구분했다. 상위 Service가 하위 도메인의 Repository를 가지는 것 대신 Service 가지게 되었고, Repository(DAO) 계층에 접근하는 부분을 덜어낼 수 있었다. 예를 들어 노선에 역을 추가할 때, StationRepository에 역을 생성해달라는 요청을 전달하지 않고 StationService에 역을 생성해달라는 요청을 전달한다.
역을 추가하거나 삭제하는 과정은 지하철 노선의 핵심 비즈니스 로직이다. 핵심 비즈니스 로직을 Service가 직접 할 수 있도록 하며, 해당 도메인의 생명주기는 해당 서비스에서만 관리할 수 있게 되었다. 서비스가 서비스를 가지도록 설계를 허용한다면 반드시 의존성을 한 방향으로 정리해야 한다는 것을 염두에 두어야 한다. 의도하지는 않았겠지만 A라는 서비스와 B라는 서비스가 서로를 참조하는 구조가 발생할 수 있다.
순환참조가 발생하는 상황이 생기면 내가 설계를 잘 하고 있나? 라는 의심을 해봐야한다. 일반적으로 생기는 구조가 아니기 때문에 순환참조가 발생한다라면 잘못된 설계를 한 것이 아닌지 고민해보면 해결할 수 있지 않을까 생각이 든다.
Conclusion
구조를 변경해보며 새로운 인사이트를 얻을 수 있었다. Repository(DAO) 계층은 db에 관련된 데이터이기 때문에 한 곳에서만 관리가 된다는 장점을 느꼈다. 또한 도메인이라고 하는것이 비지니스 로직을 담당하는 것이고 해당 메서드, 로직들을 서비스 계층을 사용함으로써 좀 더 자연스럽게 녹여낼 수 있다는 장점도 느꼈다. 예를 들어 order(주문) 도메인이 있고 revenue(매출) service가 있다고 한다면 주문의 비용 계산 로직을 호출할 수 있을 것이다.
이 과정에서 또 다시 느낀 점은 개발하려는 애플리케이션의 규모에 맞게 설계하는 것이 중요하다는 것이다.
규모가 작고 간단한 애플리케이션의 경우 Repository를 의존하게 되면 Repository에서 가져온 다른 도메인들을 쉽게 사용할 수 있고, 서비스 자체가 많지 않을 것이라고 생각하기 때문에 관리 포인트가 크지 않을 것이라고 생각된다. 또한 의존관계가 단방향으로만 흐르기 때문에 순환참조를 걱정할 필요가 없을 것이다.
요약해보면
service 참조
- 서비스와 서비스 사이에서 비즈니스 로직을 적절히 사용하는데 집중할 수 있다.
- 데이터를 다루는 책임이 분산되지 않고 관리하기 편하다.
- 순환 참조가 발생할 가능성이 있다.
- 의존 관계를 항상 고려하며 설계해야 하는 비용이 발생한다.
Repository 참조
- 여러 서비스에서 데이터를 쉽게 불러올 수 있다.
- service -> repository 라는 단방향 계층으로 순환 참조를 걱정할 필요가 없다.
- 데이터를 가져오는 로직이 여러 서비스에 분산되어서 관리하기 어려울 수 있다.
설계는 항상 정답이 없고, 애플리케이션의 상황과 규모에 맞도록 하는 것이 가장 중요하다고 생각합니다 !
'Spring' 카테고리의 다른 글
[Spring] Logback을 이용해 운영 환경 별 로그 남기기 (2) | 2023.08.14 |
---|---|
[JPA] @NotNull과 nullable = false는 어떤 차이가 있을까? (0) | 2023.07.12 |
[Spring] 필터(filter)와 인터셉터(interceptor)의 특징과 차이점 (4) | 2023.05.08 |
[Spring] Mock 객체의 리턴값이 왜 null이 나올까? (1) | 2023.04.23 |
[Querydsl] Unable to load class 'com.mysema.codegen.model.Type' 에러 해결 방법 (0) | 2023.01.12 |
- Total
- Today
- Yesterday
- 스프링 Logback
- 알림개선기
- ZNS
- 스프링 부트
- 스프링MVC
- 우테코 회고
- 네트워크
- 피움 6주차 회고
- 백준
- 알림기능개선기
- 2차 데모데이
- 팀프로젝트
- 5주차 회고
- 파이썬
- Spring
- 프로젝트
- jpa
- 런칭 페스티벌
- dm-zoned
- 환경 별 로깅 전략 분리
- dm-zoned 코드분석
- 3차 데모데이
- ZNS SSD
- 스프링 프레임워크
- 피움
- CI/CD
- 회고
- java
- 8주차 회고
- 우테코
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |