////
Search
Duplicate
🚞

Chapter 07. 서비스

앞서 서비스의 역할은 도메인 객체나 도메인 서비스라고 불리는 도메인에 일을 위임하는 공간이어야 한다고 했다. 이는 서비스가 다음과 같이 크게 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로 만든다.
서비스에 세터가 존재한다면 지운다.
서비스는 반드시 생성자 주입으로 바꾼다.
서비스의 비즈니스 로직을 도메인에 양보한다.
서비스를 얇게 유지한다.