Hyun's Wonderwall

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 6. 응용 서비스와 표현 영역 본문

Study/Java, Spring

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 6. 응용 서비스와 표현 영역

Hyun_! 2024. 3. 25. 21:58

EFUB 4기 BackEnd Lead_ 도메인 주도 개발 스터디

  • 스터디 커리큘럼: 최범균, "도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지"
  • 3주차 과제: Chapter 5. 스프링 데이터 JPA를 이용한 조회 기능, Chapter 6. 응용 서비스와 표현 영역

Chapter 6. 응용 서비스와 표현 영역

  • Keywords: 응용 서비스 구현, 표현 영역의 역할, 값 검증과 권한 검사

6.1 표현 영역과 응용 영역

그동안 도메인 영역 부분을 배웠는데, 도메인이 제 기능을 하려면 사용자와 도메인을 연결해 주는 매개체가 필요하다. => 응용 영역과 표현 영역의 역할.

[ 사용자 -> 응용 영역 -> 표현 영역 -> 도메인 영역 ]

표현 영역: 사용자 요청 해석. HTTP 요청 받으면 URL, 요청 파라미터, 쿠키, 헤더 등을 이용해서 사용자가 실행하고 싶은 기능을 판별하고 그 기능을 제공하는 응용 서비스를 실행한다.

응용 영역: 실제 사용자가 원하는 기능을 제공. 응용 서비스는 기능을 실행하는 데 필요한 입력 값을 메서드 인자로 받고 실행 결과를 리턴한다.

 

응용 서비스의 메서드가 요구하는 파라미터와 표현 영역이 사용자로부터 전달받은 데이터는 형식이 일치하지 않는다. 때문에 표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.

ex. 표현 영역의 코드 - 응용 서비스가 요구하는 JoinRequest 객체를 생성한 뒤 이를 이용해 응용 서비스의 메서드를 호출한다.

 

응용 서비스를 실행한 뒤 표현 영역은 실행 결과를 사용자에게 알맞은 형식으로 응답한다.

- 사용자 요청에 맞게 HTML이나 JSON 형식으로 응답

 

사용자와 상호작용은 표현 영역이 처리하기 때문에, 응용 서비스는 표현 영역에 의존하지 않는다.

단지 기능 실행에 필요한 입력 값을 받고 실행 결과를 리턴하면 될 뿐이다.

6.2 응용 서비스의 역할

응용 서비스는 클라이언트가 요청한 기능을 실행한다. 응용 서비스는 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다.

 

주요 역할: 도메인 객체 사용해 사용자 요청 처리

이므로 응용 서비스는 도메인-표현 연결 창구

주로 도메인 객체 간의 흐름을 제어하기 때문에 단순한 형태를 갖는다.

1. 리포지터리에서 애그리거트를 구한다

2. 애그리거트의 도메인 기능을 실행한다

3. 결과를 리턴한다.

새로운 애그리거트를 생성하는 응용 서비스 역시 간단하다

1. 데이터 중복 등 데이터가 유효한지 검사한다

2. 애그리거트를 생성한다.

3. 레포지터리에 애그리거트를 저장한다

4. 결과를 리턴한다.

 

응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다!!

응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.

 

응용 서비스의 주요 역할: 트랜잭션 처리, 접근 제어, 이벤트 처리 등

-응용 서비스는 도메인의 상태 변경을 트랜잭션으로 해야 한다. 데이터 일관성 위해!!

 

6.2.1 도메인 로직 넣지 않기

도메인 로직은 도메인 영역에 위치하고 응용 서비스는 도메인 로직을 구현하지 않는다고 했다.

암호 변경 기능을 위한 응용 서비스는 Member 애그리거트의 관련 리포지터리를 이용한다.

Member 애그리거트는 암호를 변경하기 전에 기존 암호를 올바르게 입력했는지 확인하는 로직을 구현한다.

기존 암호를 올바르게 입력했는지 확인하는 것은 도메인의 핵심 로직이기 떄문에 응용 서비스에서 구현하면 안 된다.

 

도메인 로직을 도메인 영역과 응용 서비스 영역에 분산해서 구현하면 코드 품질에 문제 발생!!

1. 코드의 응집성이 떨어진다. - 도메인 데이터와 그 데이터를 조직하는 도메인 로직이 한 영역에 위치하지 않고 서로 다른 영역에 위치한다는 것은, 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다는 것을 의미한다.

2. 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다. (중복)

=> 변경 용이성이 떨어진다,,,

도메인 로직을 도메인 영역에 모아서 코드 중복을 줄이고 응집도를 높여야 한다.

6.3 응용 서비스의 구현

응용 서비스는 디자인 패턴에서 facade와 같은 역할이다.

복잡한 로직을 수행하지 않아서 구현이 어렵지 않다.

이 절에서는 응용 서비스를 구현 할 때 몇 가지 고려할 사항과 트랜잭션과 같은 구현 기술의 연동에 대해 살펴봄.

6.3.1 응용 서비스의 크기

회원 도메인을 생각해 보면 회원 가입하기, 회원 탈퇴하기, 회원 암호 변경하기, 비밀번호 초기화하기와 같은 기능을 구현하기 위해 도메인 모델을 사용한다.

 

구현 방법

1. 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현
- 장점: 동일 로직에 대한 코드 중복 제거 가능.
- 단점: 한 서비스 클래스의 크기(코드 줄 수)가 커진다. 연관성이 적은 코드에 한 클래스에 함꼐 위치 => 결과적으로 관련 없는 코드가 뒤섞여 코드를 이해하는 데 방해가 된다.

2. 구분되는 기능별로 응용 서비스 클래스 따로 구현
- 한 응용 서비스 클래스에서 1~3개의 기능을 구현. ex 암호 변경 기능만을 위한 응용 서비스 클래스를 별도 구현

- 장점: 코드 품질 유지에 도움. 다른 기능을 구현한 코드에 영향 받지 않음.

- 단점: 클래스 개수 많아짐. 각 기능마다 동일한 로직을 구현할 경우 동일 코드 중복 구현 가능성이 있음. -> 별도 클래스에 공통 로직을 정적 메서드로 구현해서 해결 가능.

작가는 2번을 선호함.

6.3.2 응용 서비스의 인터페이스와 클래스

인터페이스를 만들도 이를 상속한 클래스를 만드는 것이 필요할까?

인터페이스와 클래스를 이렇게 따로 구현하면 소스 파일만 많아지고 구현 클래스에 대한 간점 참조가 증가해서 전체 구조가 복잡해진다.

테스트 주도 개발(TDD)를 즐겨 하고 표현 영역부터 개발을 시작한다면 응용 서비스의 인터페이스부터 작성하게 될 것이다.

But! 표현 영역이 아닌 도메인 영역이나 응용 영역의 개발을 먼저 시작하면 응용 서비스 클래스가 먼저 만들어진다. Mockito는 클래스에 대해서도 테스트용 대역 객체를 만들 수 있어 응용 서비스에 대한 인터페이스가 없어도 표현 영역을 테스트할 수 있다.

 

6.3.3 메서드 파라미터와 값 리턴

응용 서비스가 제공하는 메서드는 도메인을 이용해서 사용자가 요구한 기능을 실행하는 데 필요한 값을 파라미터로 전달받아야 한다. 예를 들어 암호 변경 응용 서비스는 암호 변경 기능을 구현하는 데 필요한 회원 ID, 현재 암호, 변경할 암호를 파라미터로 전달받는다.

개별 파라미터로 전달받을 수도 있고 값 전달을 위해 별도 데이터 클래스를 만들어 전달받을 수도 있다.

 

응용 서비스는 파라미터로 전달받은 데이터를 사용해서 필요한 기능을 구현하면 된다.

 

스프링 MVC와 같은 웹 프레임워크는 웹 요청 파라미터를 자바 객체로 변환하는 기능을 제공하므로 응용 서비스에 데이터로 전달할 요청 파라미터가 두 개 이상 존재하면 데이터 전달을 위한 별도 클래스를 사용하는 것이 편리하다.

 

응용 서비스의 결과를 표현 영역에서 사용해야 하면 응용 서비스 메서드의 결과로 필요한 데이터를 리턴한다.

결과 데이터가 필요한 대표적인 예가 식별자다. 온라인 쇼핑몰은 주문 후 주문 상세 내역을 볼 수 있는 링크를 바로 보여준다. 이 링크를 제공하려면 방금 요청한 주문의 번호를 알아야 한다. 이 요구를 충족하려면 주문 응용 서비스는 주문 요청 처리 후에 주문 번호를 결과로 리턴해야 한다.

 

응용 서비스에서 애그리거트 자체를 리턴하면 도메인 로직 실행을 표현 영역에서 하게 되어버릴 수도 있는 문제가 있다. 코드 응집도가 낮아진다. So 응용 서비스는 표현 영역에서 필요한 데이터만 리턴하는 것이 좋다.

6.3.4 표현 영역에 의존하지 않기

파라미터로 표현 영역에 해당하는 객체를 전달하면 안 된다. HttpServletRequest나 HttpSession 같은.

응용 서비스는 표현 영역 기술 쓰지 않도록 해야한다.

6.3.5 트랜잭션 처리

스프링은 @Transactional 적용된 메서드가 RuntimeException 발생시키면 트랜잭션을 롤백하고 그렇지 않으면 커밋한다.

 

6.4 표현 영역

표현 영역의 책임:

1. 사용자가 시스템을 사용할 수 있는 흐름을 제공하고 제어.

2. 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공.

3. 사용자 세션 관리

- 웹: 쿠키, 서버 세션을 이용해 사용자의 연결 상태를 관리한다.

6.5 값 검증

표현 영역, 응용 서비스 두 곳에서 모두 수행 가능; 원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리함.

응용 서비스에서 에러 코드를 모아 하나의 익셉션을 발생시킬 수도 있음.

=> 표현 영역은 응용 서비스가 그 익셉션을 발생시키면 익셉션에서 에러 목록을 가져와 표현 영역에서 사용할 형태로 변환 처리함.

6.6 권한 검사

권한 검사 할 수 있는 위치: 표현 영역, 응용 서비스, 도메인

특정 경로 url을 인증된 사용자만 접근할 수 있어야 하는데 이것에 대해 표현 영역은 다음과 같이 행동을 해야 한다

(1) url을 처리하는 컨트롤러에 웹 요청을 전달하기 전에 인증 여부를 검사해 인증된 사용자의 웹 요청만 컨트롤러에 전달

(2) 인증된 사용자가 아닐 경우 로그인 화면으로 리다이렉트

 

서블릿 필터 위치에서 이런 접근 제어를 하기에 좋다.

인증 여부 뿐만 아니라 권한에 대해서 동일한 방식으로 필터를 사용해 url별 권한 검사를 할 수 있다.

스프링 시큐리티는 이와 유사한 방식으로 필터를 이용해 인증 정보를 생성하고 웹 접근을 제어한다.

6.7 조회 전용 기능과 응용 서비스

응용 서비스 필요 없다면 굳이 만들지 않아도 된다.