•
앞서 서비스의 역할은 도메인 객체나 도메인 서비스라고 불리는 도메인에 일을 위임하는 공간이어야 한다고 했다. 이는 서비스가 다음과 같이 크게 3가지 종류의 일을 수생한다는 의미다.
◦
도메인 객체를 적재한다.
◦
도메인 객체나 도메인 서비스에 일을 위임한다.
◦
도메인 객체의 변경 사항을 저장한다.
•
이번 장에서는 다음과 같은 내용들을 알아보자.
◦
서비스 컴포넌트가 왜 이것을 담당하는 지
◦
도메인 서비스란 무엇이며 스프링에서 서비스는 왜 서비스라고 부르는지
◦
서비스 컴포넌트에게 추가적으로 기대되는 역할이나 책임은 무엇인지
1. Manager
•
대부분의 개발자가 서비스라는 컴포넌트롤 다룸에도 이 컴포넌트를 왜 서비스라고 부르는지 알지 못한다. 그리고 정확히 무엇을 처리하는 공간인지도 모른다.
◦
그래서인지 나도 그간 답변으로 서비스는 비즈니스 로직을 처리하는 계층이라는 식의 답변을 했었다.
◦
이는 충분하지 못한 답변으로 서비스가 무엇인가요라는 질문에 서비스는 서비스라는 동문서답을 하는 것과 크게 다를 바 없다.
•
이에 대한 해답으로 @Service 어노테이션이 작성된 실제 코드를 찾아보면 해답을 알 수 있다.
◦
해당 어노테이션에 작성된 주석 내용을 번역하면 다음과 같다.
이 어노테이션은 어노테이션이 지정된 클래스가 서비스임을 지칭하는 데 사용한다. 서비스는 DDD에서 시작된 개념으로 캡슐화된 상태없이 모델과는 독립된 동작을 제공하는 인터페이스다.
혹은 이 어노테이션이 지정된 클래스는 J2EE 패턴 중 하나인 비즈니스 서비스 파사드나 그와 유사한 것처럼 사용될 수 있음을 의미한다. 이 어노테이션은 매우 일반적인 용어로 각자의 사정에 맞게 좁혀 사용할 수 있다.
•
주석을 읽어보면 스프링의 @Service 어노테이션은 DDD에서 영감을 받아 만들어진 것임을 알 수 있다.
•
보통 도메인 주도 설계라고 풀이되는 DDD는 말 그대로 도메인을 중심에 두고 소프트웨어를 설계하는 방식을 선택한다.
◦
이 방법론에서는 개발자가 복잡한 도메인을 이해하고 설계하는 방법을 알려준다. 그리고 도메인 전문가라고 부르는 사람과 소통하는 방법, 도메인 문제를 해결할 설계 패턴, 도메인 모델링 방법 등을 알려준다.
•
앞서 우리는 도메인이 비즈니스 영역이라고 설명했다. 여기에 설명을 보태면 도메인이란 비즈니스 영역이자 우리가 해결하고 싶은 문제 영역이다.
◦
일반적으로 도메인 영역에 대한 지식은 도메인 전문가가 잘 알고 있기 때문에 DDD에선 도메인 전문가와 개발자 간의 협력을 강조한다.
•
그렇다면 DDD에서 정의하는 서비스란 무엇일까? DDD의 창시자 에릭 에반스가 서비스를 설명하면서 작성한 글을 살펴보자.
◦
자신의 본거지를 ENTITY나 VALUE OBJECT에서 찾지 못하는 중요한 도메인 연산이 있다. 이들 중 일부는 본질적으로 사물이 아닌 활동이나 행위인데, 우리의 모델링 패러다임이 객체이므로 그러한 연산도 객체와 잘 어울리게끔 노력해야 한다.
•
이것이 바로 서비스의 정체다. 서비스는 도메인 객체가 처리하기 애매한 연산 자체를 표현하기 위한 컴포넌트다.
◦
즉, 도메인 영역도 비즈니스 로직이고 서비스 영역도 비즈니스 로직이다.
⇒ (여기서부턴 제 개인적인 생각입니다.) 다만 책임을 수행할 객체를 찾기 힘들만큼 그 책임이 광범위하거나 협력이 광범위한 경우, 이 행위에 대한 책임을 서비스가 가져가게 되는 것 같다.
•
여기서 저자는 이런 광범위한 책임을 Manager라는 일종의 서비스 개념을 사용해서 처리한다. 즉 UserService 대신 UserManager라는 네이밍을 선택하는 것이다.
◦
이를 이용하면 특정 모델에 강하게 연관되어 있으며 부가적인 논리 로직을 제공하는 공간으로 해석되기 쉽다.
•
내, 외부 분리가 한 번 더 발생하는데, ProductService에서 상품 가격 계산에 PriceManager을 사용하는 것이다.
◦
여기서 ProductService는 좀 더 어플리케이션에 가까운, 즉 외부에 가까운 영역이고 Manager는 도메인 시스템을 총괄하는, 좀 더 내부적인 클래스다.
◦
즉, 목적이 다르므로 이 둘을 구분하는데, 이때 외부의 서비스를 어플리케이션 서비스라고 부르고 내부의 서비스를 도메인 서비스라고 부른다.
•
지금까지 학습한 내용을 한 번 정리해보자.
◦
스프링의 서비스는 DDD의 서비스에서 유래했다.
◦
DDD에서 도메인은 비즈니스 영역이며 문제 영역이다.
◦
DDD에서 서비스는 도메인 문제를 해결하기 위한 패턴 중 하나다.
◦
서비스는 객체가 처리하기에 애매한 연산 로직을 갖고 있는 컴포넌트다.
◦
도메인 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스를 도메인 서비스라 한다.
◦
어플리케이션 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스를 어플리케이션 서비스라 한다.
분류 | 역할 | 주요 행동 | 예시 |
도메인 | 비즈니스 로직을 처리 | 도메인 역할을 수행한다.
다른 도메인과 협력한다. | User, Product, Coupon |
도메인 서비스 | 비즈니스 연산 로직을 처리 | 도메인 협력을 중재한다.
도메인 객체에 기술할 수 없는 연산 로직을 처리한다. | PriceManager |
어플리케이션 서비스 | 어플리케이션 연산 로직을 처리 | 도메인을 저장소에서 불러온다.
도메인 서비스를 실행한다.
도메인을 실행한다. | ProductService |
•
이 셋을 구분하고 각 역할을 이해하는 것이 중요하다. 많은 스프링 개발자들이 비즈니스 로직이라는 이름하에 서비스 컴포넌트에 모든 코드를 작성한다.
◦
그렇다면 이런 모든 복잡한 로직을 도메인에서 처리하게 된다면 어플리케이션 서비스는 무슨 일을 담당하게 될까?
◦
도메인 서비스의 파사드처럼 사용할 수 있게 객체를 적재하고 일을 시키고 객체를 반환하는 정도의 일만 수행하게 될 것이다.
•
다시 말해 스프링 서비스는 도메인과 도메인 서비스의 파사드처럼 사용할 수 있는 공간이다. 비즈니스 로직을 갖고 있는 곳이 도메인과 도메인 서비스이기 때문이다.
◦
딱 이정도의 책임만 스프링 서비스는 가져가야 한다.
2. 서비스보다 도메인 모델
•
무심코 도메인 서비스를 적용하다보면 분명 도메인으로 만들 수 있음에도 도메인 서비스로 적용하게 되는 경우가 생길 수 있다.
•
앞서 PriceManager는 Cashier라는 도메인 객체로 대체될 수 있다. 둘이 뭐가 다른가 싶을 순 있지만 미래에는 많이 달라질 수 있다.
◦
PriceManager는 가격과 관련된 단순 연산들이 자리하게 될 것이고 Cashier에는 매장 관리라던가 객체의 이름에 적합한 책임들이 자리하게 될 것이다.
•
여기서 얻을 수 있는 교훈이 하나 있다. 도메인과 도메인 서비스는 이름으로 결정되는 것이 아니라는 것이다. 도메인과 도메인 서비스를 구분짓는 것은 행동으로 결정된다.
•
여기서 저자는 객체지향을 추구하는 스프링 개발자가 서비스를 어떻게 다루면 좋을지에 대한 조언을 제공한다.
◦
서비스는 가능한 적게 만들고 얇게 유지한다.
▪
서비스를 얇게 유지하라는 말은 서비스에 있는 비즈니스 로직을 가급적 도메인 객체로 옮기라는 말과 같다.
▪
즉 서비스 코드를 작성하고 있다면 이 코드가 기존 도메인 객체에 속하지는 않는지 혹은 새로운 도메인 모델로 만들 수는 없는지를 고민해보는 것이 좋다.
◦
서비스보다 풍부한 도메인 모델을 만든다.
•
이에 의해 개발 우선순위는 다음과 같아진다. 도메인 모델 > 도메인 서비스 > 어플리케이션 서비스
◦
가능한 한 앞에 있는 클래스로 먼저 만들려고 노력하면 된다. 이러한 개발 우선순위를 사용할 때 얻을 수 있는 또 다른 이점은 코드의 의존성이 명확하게 드러나기 때문에 테스트 작성이 용이해진다는 것이다.
3. 작은 기계
•
다음으로 저자는 서비스에 관한 새로운 시각을 하나 소개한다.
첫 번째 객체는 한번 생성하면 어러 번 사용하지만 그 자신은 바꿀 수 없다. 생명 주기도 매우 단순하다. 한번 생성하면 특정 작업을 하는 작은 기계처럼 영원히 실행할 수 있다. 이러한 객체를 서비스라 한다.
•
이를 정리해보면 다음과 같다.
◦
서비스는 한번 생성하면 여러 번 사용하지만 그 자신은 바뀌지 않는다.
◦
서비스는 작은 기계처럼 영원히 실행할 수 있다.
•
앞선 말은 서비스는 불변해야 한다는 것을 가리킨다. 애초에 서비스는 객체로 표현하기 애매한 연산 로직의 집합이다.
•
다시 말해 서비스는 어떤 가변 상태를 가지는 것이 아닌 계산식 그 자체다. 그러니 서비스는 불변이어야 한다. 서비스 메소드의 실행은 항상 같은 논리를 사용한다. 그래서 입출력이 일정해야 한다.
•
이런 흐름에서 이해해볼 수 있는 격언이 하나 있는데, 서비스에 의존성을 주입할 때, 필드, 세터 주입 대신 생성자 주입을 사용하라는 것이다.
•
보통 다음의 이유를 들면서 둘을 지양하고 생성자 주입을 사용하라고 한다.
◦
생성자 주입을 사용하면 명시적으로 의존성을 표현할 수 있다.
◦
생성자 주입을 사용하면 테스트하기가 쉬워진다.
◦
생성자 주입을 사용하면 순환 의존성을 방지할 수 있다.
•
이는 생성자 주입을 사용했을 때 얻을 수 있는 부수적인 이점일 뿐이다. 생성자 주입을 사용해야 하는 조금 더 근본적인 이유는 서비스가 원래 불변해야 되기 때문이다.
4. 조언
•
저자는 서비스를 적절히 다루기 위한 조언들을 제공한다.
◦
서비스의 멤버 변수는 모두 final로 만든다.
◦
서비스에 세터가 존재한다면 지운다.
◦
서비스는 반드시 생성자 주입으로 바꾼다.
◦
서비스의 비즈니스 로직을 도메인에 양보한다.
◦
서비스를 얇게 유지한다.