Blog

도메인 주도 개발 시작하기(6)

도메인 주도 개발 시작하기(6)

6-1. 표현 영역과 응용 영역

도메인이 제 기능을 하려면 사용자와 도메인을 연결해주는 매개체가 필요하다
응용 영역과 표현 영역이 사용자와 도메인을 연결해 주는 매개체 역할을 한다
flowchart LR
	user --> presentation(표현 영역)
	presentation --> application(응용 영역)
	application --> domain(도메인 영역)
Mermaid
복사
표현 영역은 사용자의 요청을 해석하여 사용자가 실행하고 싶은 기능을 판별하고 그 기능을 제공하는 응용 서비스를 실행한다
실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스다.
응용 서비스의 메서드가 요구하는 파라미터와 표현 영역이 사용자로부터 전달받은 데이터는 형식이 일치하지 않기 때문에 표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다
사용자와 상호 작용은 표현 영역이 처리하기 때문에, 응용 서비스는 표현 영역에 의존하지 않는다.
응용 영역은 사용자가 웹브라우저를 사용하는지, REST API 를 사용하는지, TCP 소켓을 사용하는지를 알 필요가 없다.

6-2. 응용 서비스의 역할

응용 서비스는 사용자가 요청한 기능을 실행한다.
사용자의 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다
주요 역할은 도메인 객체를 사요해서 사용자의 요청을 처리하는 것이므로 표현 영역 입장에서 보았을 때 응용 서비스는 도메인 영역과 표현 영역을 연결해주는 창구 역할을 한다
응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기 때문에 다음과 같이 단순한 형태를 갖는다
예시 코드
 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다.
응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.
응용 서비스는 트랜잭션 처리도 담당한다.
도메인의 상태 변경을 트랜잭션으로 처리해야 한다.

6-2-1. 도메인 로직 넣지 않기

도메인 로직은 도메인 영역에 위치하고, 응용 서비스는 도메인 로직을 구현하지 않는다.
도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.
1.
코드의 응집성이 떨어진다
도메인 데이터와 그 데이터를 조작하는 도메인 로직이 한 영역에 위치하지 않고 서로 다른 영역에 위치한다는 것은 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다는 의미이다.
2.
여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.
코드 중복을 위해 응용 서비스 영역에서 별도의 보조 클래스를 만들 수 있지만, 애초에 도메인 영역에 구현되어 있으면 응용 서비스는 그 기능을 이용하기만 하면 된다
일부 도메인 로직이 응용 서비스에 출현하면서 두 가지 문제는 결과적으로 코드 변경을 어렵게 만든다.
소프트웨어가 가져야 할 중요한 경쟁 요소 중 하는 변경 용이성인데, 변경이 어렵다는 것은 그만큼 소프트웨어의 가치가 떨어진다는 것을 의미한다.

6-3. 응용 서비스의 구현

응용 서비스는 표현 영역과 도메인 영역을 연결하는 매개체 역할을 하는데, 이는 디자인 패턴에서 파사드(facade) 와 같은 역할을 한다.
응용 서비스 자체는 복잡한 로직을 수행하지 않기 때문에 응용 서비스의 구현은 어렵지 않다.

6-3-1. 응용 서비스의 크기

필자는 한 클래스가 여러 역할을 갖는 것 보다 각 클래스마다 구분되는 역할을 갖는 것을 선호한다.
응용 서비스는 보통 2가지 방법 중 한가지 방식으로 구현한다
1.
한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
각 기능에서 동일한 로직에 대한 코드 중복을 제거할 수 있는 장점이 있다
중복되는 로직을 private 메서드로 구현하고 이를 호출하는 방법으로 중복 로직을 제거할 수 있다
한 서비스 클래스의 크기 (코드 라인 수) 가 커진다는 것은 이 방식의 단점이다
코드 크기가 커지면 연관성이 적은 코드가 한 클래스에 함께 위치할 가능성이 높아지게 된다
결과적으로 관련 없는 코드가 뒤섞여 코드를 이해하는데 방해가 된다
한 클래스에 코드가 모이기 시작하면 엄연히 분리하는 것이 좋은 상황임에도 습관적으로 기존에 존재하는 클래스에 억지로 끼워넣게 된다
이것은 코드를 점점 얽히게 만들어 코드 품질을 낮추는 결과를 초래한다
2.
구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
구분되는 기능별로 서비스 클래스를 구현하는 방식은 한 응용 서비스클래스에서 1개 ~ 3개의 기능을 구현한다
클래스 개수는 많아지지만, 한 클래스에 관련된 기능을 모두 구현하는것과 비교해서 코드 품질을 일정 수준으로 유지하는데 도움이 된다
각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다
각 기능마다 동일한 로직을 구현할 경우 여러 클래스에 중복해서 동일한 코드를 구현할 가능성이 있는데, 이는 별도의 클래스에 로직을 구현해서 코드가 중복되는 것을 방지할 수 있다

6-3-2. 응용 서비스의 인터페이스와 클래스

응용 서비스를 구현할 때 인터페이스가 필요한 몇가지 상황이 있다
대표적인 예시) 구현 클래스가 여러 개인 경우
구현 클래스가 다수 존재하거나, 런타임에 구현 객체를 교체해야 할 때 인터페이스를 유용하게 사용할 수 있다
하지만, 응용 서비스는 런타임에 교체하는 경우가 거의 없고, 한 응용 서비스의 구현 클래스가 두 개인 경우도 드물다.
인터페이스와 클래스를 따로 구현하면 소스 파일만 많아지고, 구현 클래스에 대한 간접 참조가 증가해서 전체 구조가 복잡해진다.
따라서 인터페이스가 명확하게 필요하기 전까지는 응용 서비스에 대한 인터페이스를 작성하는 것이 좋은 선택이라고 볼 수는 없다.

6-3-3. 메서드 파라미터와 값 리턴

응용 서비스가 제공하는 메서드는 도메인을 이용해서 사용자가 요구한 기능을 실행하는데 필요한 값을 파라미터로 전달받아야 한다.
응용 서비스에 데이터로 전달할 요청 파라미터가 2개 이상 존재하면 데이터 전달을 위한 별도 클래스를 사용하는 것이 편리하다
응용 서비스의 결과를 표현 영역에서 사용해야 하면 응용 서비스 메서드의 결과로 필요한 데이터를 리턴해야 한다.
결과 데이터가 필요한 대표적인 예가 식별자다. ( 유저ID, 주문번호 등 )
표현 영역 코드는 응용 서비스가 리턴한 주문 애그리거트 객체에서 주문번호를 구해 사용제에게 보여줄 응답 화면을 생성하면 된다.
응용 서비스에서 애그리거트 자체를 리턴하면 코딩은 편할 수 있지만, 도메인 로직 실행을 응용 서비스와 표현 영역 두 곳에서 할 수 있게 된다.
이것은 기능 실행 로직을 응용 서비스와 표현 영역에 분산시켜 코드의 응집도를 낮추는 원인이 된다.
응용 서비스는 표현 영역에서 필요한 데이터만 리턴하는 것이 기능 실행 로직의 응집도를 높이는 확실한 방법이다.

6-3-4. 표현 영역에 의존하지 않기

응용 서비스 파라미터 타입을 결정할 때 주의할 점은 표현 영역과 관련된 타입을 사용하면 안된다.
HttpServletRequestHttpSession, HttpCookie 등을 전달하면 안된다.
응용 서비스에서 표현 영역에 대한 의존이 발생하면 응용 서비스만 단독으로 테스트하기 어려워진다.
표현 영역의 구현이 변경되면 응용 서비스의 구현도 함께 변경해야 하는 문제도 발생한다
가장 큰 문제점: 응용 서비스가 표현 영역의 역할까지 대신하는 상황이 벌어질 수 있다.
표현 영역의 응집도가 깨진다
결과적으로 코드 유지 보수 비용을 증가시키는 원인이 된다

6-3-5. 트랜잭션 처리

트랜잭션을 관리하는 것은 응용 서비스의 중요한 역할이다.
프레임워크가 제공하는 트랜잭션 기능을 적극 사용하는 것이 좋다.
트랜잭션을 시작하여 커밋하고, Exception 이 발생하면 롤백할 수 있다.

6-4. 표현 영역

표현 영역의 책임

1.
사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다
표현 영역은 응용 서비스를 이용해서 표현 영역의 요청을 처리하고 그 결과를 응답으로 전송한다
2.
사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다
표현 영역은 사용자의 요청 데이터를 응용 서비스가 요구하는 형식으로 변환하고, 응용 서비스의 결과를 사용자에게 응답할 수 있는 형식으로 반환한다.
표현 영역의 뷰는 이 에러 코드에 알맞은 처리 (해당하는 메시지 출력과 같은)를 하게 된다.
3.
사용자의 세션을 관리한다
웹은 쿠키나 서버 세션을 이용해서 사용자의 연결 상태를 관리한다.
세션 관리는 권한 검사와도 연결된다.

6-5. 값 검증

값 검증은 표현 영역응용 서비스 두 곳에서 모두 수행할 수 있다.
원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리한다.
사용자 불편을 해소하기 위해 응용 서비스에서 에러 코드를 모아 하나의 익셉션으로 발생시키는 방법도 있다.
표현 영역에서 필수 값을 검증하는 방법도 있다.
스프링과 같은 프레임워크에서는 값 검증을 위한 Validator 인터페이스를 별도로 제공하므로 이 인터페이스를 구현한 검증기를 따로 구현할 수 있다.
표현 영역과 응용 서비스가 값 검사를 나눠서 수행한다
표현 영역에서 필수 값과 값의 형식을 검증하면 실질적으로 응용 서비스는 ID 중복 여부와 같은 논리적 오류만 검사하면 된다
표현 영역: 필수 값, 값의 형식, 범위 등을 검증
응용 서비스: 데이터의 존재 유무와 같은 논리적 오류 검증
응용 서비스에서 값 검증을 모두 처리하게되면 작성할 코드가 늘어나는 단점은 있지만 응용 서비스의 완성도가 높아지는 이점도 있다.

6-6. 권한 검사

보통 3곳에서 권한 검사를 수행할 수 있다

1. 표현 영역

URL 만으로 접근 제어를 할 수 없는 경우 응용 서비스의 메서드 단위로 권한 검사를 수행해야 한다
기본적인 검사는 인증된 사용자인지 아닌지 검사한다
URL 을 처리하는 컨트롤러에 웹 요청을 전달하기 전에 인증 여부를 검사해서 인증된 사용자의 웹 요청만 컨트롤러에 전달한다
인증된 사용자가 아닐 경우 로그인 화면으로 리다이렉트 시킨다.

2. 응용 서비스

꼭 응용 서비스의 코드에서 직접 권한 검사를 해야 한다는 것을 의미하진 않는다
스프링 시큐티리의는 AOP 를 활용해서 Annotation 으로 서비스 메서드에 대한 권한 검사를 할 수 있는 기능을 제공한다

3. 도메인

개별 도메인 객체 단위로 권한 검사를 해야 하는 경우는 구현이 복잡해진다
도메인 확인해야 하는 권한이 다르므로 도메인에 맞게 보안 프레임워크를 확장하거나, 직접 구현해야 한다.

6-7. 조회 전용 기능과 응용 서비스

서비스에서 수해앟는 추가적인 로직이 없을 뿐더러 단일 쿼리만 실행하는 조회 전용 기능이어서 트랜잭션이 필요하지 않다.
이 경우라면 굳이 서비스를 만들 필요 없이 표현 영역에서 바로 조회 전용 기능을 사용해도 문제가 없다.
응용 서비스를 항상 만들었던 개발자는 컨트롤러와 같은 표현 영역에서 응용 서비스 없이 조회 전용 기능에 접근하는 것이 이상하게 느껴질 수 있다
하지만, 응용 서비스가 사용자 요청 기능을 실행하는 데 별다른 기여를 하지 못한다면 굳이 서비스를 만들지 않아도 괜찮다.

AS-IS

 flowchart TB
	controller(컨트롤러) --> application(조회 응용 서비스)
	application --> dao(조회 전용 기능\nDAO/리포지터리)
Mermaid
복사

TO-BE

 flowchart TB
	controller(컨트롤러) --> dao(조회 전용 기능\nDAO/리포지터리)
Mermaid
복사