오늘은 주로 사용하던 Spring Framework의 Layer인 Controller, Service, Repository 중 Service Layer에 대해 고민했던걸 써보려고 한다.
이미 흔히 알고 있지만 간략하게 설명하자면
Repository Layer의 역할도 너무나 명확하며, Spring을 사용하면서 Repository 추상화는 너무나 잘 되어있다고 생각한다.
JPA를 사용하던, MyBatis를 사용하던 너무나 명확하고 분리가 쉽다.
오늘 쓰고 싶었던 얘기를 작성하자면
1. Service Layer는 DAO와 1대1 매핑일까?
2. Service Layer는 다른 Service Layer에 대해서 종속성을 가지고 있어도 문제가 없는가?
이런 생각을 하게 된 이유는 최근 Spring의 Constructor Injection에 대해서 알아보고 재사용성에 대한 관심이 높아졌기 때문이다.(이전에 작성한 아무 이유 없던 filed Injection을 Constructor Injection으로 변경하려고 했더니, 시작하자마자 순환 참조가 발생했다...)
이 순환 참조를 해결하려 코드를 봤더니... Service의 종속성이 말도 안되게 넘쳐나고 있었다...
하나의 Service에서 여러개의 Service와 DAO를 참조하고 있었다...
이 코드들을 어떻게 refactoring을 할까 고민하던 도중, 문득 다른 개발자들은 Service Layer를 어떻게 사용하고 있을까라는 생각을 하게 되었다.
구글링을 한 결과 만족할 만한 글을 하나 읽었다.
글의 내용을 정리하자면
1. Service와 DAO단은 1:1 매핑
2. Service에서 Service를 참조 가능하나 계층 구조가 명확해야한다.
-> 이건 순환참조를 방지하기 위해서
Service와 DAO는 1:1 매핑에는 적극 동참한다. 그 이유는
1. Service의 위치한 Business Logic에는 데이터 유효성 검사, 무결성 검사 등 꼭 필요한 Logic들이 포함되는데, 이걸 무시하고 repository에 데이터 조작을 한다면??? -> 데이터 무결성을 보장할 수 없다.
언제든지 원치 않는 이상한 데이터가 삽입 또는 수정 될 수 있다.
2. 중복 코드 유발 -> repository에 데이터를 조작하기 위해서 business Logic이 들어가더라고 이건 분명한 중복코드이다. 이미 repository와 1:1로 매핑되는 Service Layer에서 동일한 business Logic을 구현했는데 동일한 Logic을 구현한다면? 이건 중복코드를 남발하는거다. 중복 코드의 문제점은 언제든지 놓칠 수 있다. 한곳의 business Logic만 변경되고 다른곳의 Logic이 변화되지 않는다면??? 버그를 자동생산하는 행위가 될 수 있다.
그렇다면 Service Layer에서 다른 Service Layer를 참조하는건?
명확하게 Service Layer끼리 계층이 완벽하다면 하나의 방법이라고 생각한다.
하지만 이 방법은 단점은
1. 문서, 또는 이 Application에 명확한 이해가 없으면, 언제든지 이 Layer를 파괴할 수 있다.
-> Constructor Injection을 이용해 어느정도 방어는 가능하지만 언제든지 무너질 수 있다고 생각한다.
-> 또한 명확하지 않다. 흔히 @Service Annotation을 이용해 표현하게 되는데, 동일한 어노테이션끼리 계층을 구분하기는 쉽지 않다.
그렇다면 어떤 방법이 가장 좋을까?
나는 Controller Layer를 이용하는게 가장 좋다고 생각한다.
Controller Layer에서 하나의 Operation을 하기 위해서 다양한 Service Layer에서 필요한 Object들을 조합해 하나의 Operation을 하는게 가장 이상적이라고 생각했다.
간단하게 설명을 하자면 Controller에서 하나의 operation을 하기 위해서 필요한 Object들을 각각의 Service에서 return 받아 operation을 하기 위한 Service에 전달만 해주면 되는것이다.
이 방법이 가장 좋다고 생각하는 이유는
1. 재사용성
-> 각각의 Service에서는 자기 자신의 역할만 명확하게 하면 된다. 즉 재사용성이 높아진다.
2. 중복코드 방지
3. 확장성
-> 각각 Service Logic이 확장하거나 변화해도 다른 곳에 side effect가 없기 때문에 확장에 용이하다.
-> 또한 Service의 교체도 용이하다.
지금까지 내가 작성한 code는 spring에서 추구하던 장점을 전혀 살리지 못하는 코드라고 생각한다...
Layer 명확하게만 나누어도 재사용성과 확장성이 용이해지는데...
좋은 글 감사합니다!! 덕분에 많은 공부가 되었어요😀
혹시 구글링 하여 찾으신, Service/DAO 1:1 매핑해야 한다는 글의 출처를 알 수 있을까요? 직접 읽고 공부해보고 싶네요ㅎㅎ
잘 읽었습니다. 저도 현재 비슷한 고민을 하고 있는데요,
Service Layer에서 다른 Service Layer 를 참조하는 것을 강제화하기 위해서 ArchUnit을 써볼까 생각합니다.
ArchUnit은 패키지 구조를 유닛 테스트 하는 기능인데요, 이것을 쓰면 강제화할 수 있을것같네요.
이렇게 했을 때 장점은 하위 Service Layer에서 Transactional을 걸고, 그것을 상위 Service Layer에서 호출할 수 있는 장점이 있을 것 같습니다.
@Transactional 이 proxy기반이라 public method 이면서 inner invocation이 아닐 때만 적용되니, Service Layer를 최소 두개의 계층으로 나누면 proxy 기반 annotation들을 적극적으로 활용할 수 있을 것 같습니다.
그렇지 않으면 Transaction 단위가 너무 크게 잡히거나, Controller layer에 비즈니스 로직이 노출되어야 한다는 점이 문제가 될 수 있을 것 같아요. Controller layer를 최대한 간결하게 가져가려고 노력중이거든요.
아 그리고 @Transactional뿐만 아니라 @Cacheable 같은 annotation도 좀 더 효율적으로 활용할 수 있을 것 같네요.
덕분에 읽으면서 제 생각도 정리하게 됐습니다. 감사합니다.