본문 바로가기
스터디/도메인 주도 개발 시작하기

[도메인 주도 개발 시작하기] 04. 응용 서비스와 표현 영역

by 디토20 2022. 7. 9.
반응형

 

 

 

 

[도메인 주도 개발 시작하기] 04. 응용 서비스와 표현 영역

 

 

1. 표현 영역과 응용 영역

잘 만든 도메인이 제 기능을 하려면 사용자와 도메인을 연결해주는 매개체가 필요하다. 응용 영역과 표현 영역이 사용자와 도메인을 연결해 주는 매개체 역할을 한다.

 

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

 

응용 영역에 위치한 서비스는 실제 사용자가 원하는 기능을 제공한다. 응용 서비스의 메서드가 요구하는 파라미터와 표현 영역이 사용자로부터 전달받은 데이터는 형식이 일치하지 않기 때무에 표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다. 응용 서비스를 실행한 뒤 표현 영역은 실행 결과를 사용자에게 맞는 형식으로 응답한다.

 

 

 

2. 응용 서비스의 역할

응용 서비스는 리포지터리에서 도메인 객체를 가져와 사용자가 요청한 기능을 실행한다. 응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기 때문에 단순한 형태를 갖는다. 만약 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다.

 

응용 서비스는 트랜잭션 처리도 담당한다. 트랜잭션 외에도 접근 제어와 이벤트 처리등의 역할을 한다.

 

 

2.1 응용 서비스에 도메인 로직 넣지 않기

암호 변경 기능을 예로 들어보자. Member 애그리거트는 암포를 변경하기 전에 기존 암호를 올바르게 입력했는지 확인하는 로직을 구현한다. 기존 암호 확인은 도메인의 핵심 로직이기 때문에 다음 코드 처럼 응용 서비스에서 로직을 구현하면 안된다.

public class ChangePasswordService {
  public void changePassword(String memberId, String oldPw, String newPw) {
  Member member = memberRepository.findById(memberId);
  checkMemberExist(member);
  
  if(!passwordEncoder.mathces(oldPw, member.getPassword()) {
    throw new BadPasswordException();
   }
     
  member.setPassword(newPw);
  }
....

}

 

문제점
  • 코드의 응집성이 떨어져서, 도메인 로직을 파악하기 위해 여러 영역을 분석해야 한다.
  • 여러 응용 서비스에 동일한 도메인 로직을 구현할 가능성이 높다. 
  • 결과적으로 변경이 어려워짐

 

 

 

3. 응용 서비스의 구현

3.1 응용 서비스의 크기

회원 도메인의 응용 서비스를 생각해보자. 회원 가입, 회원 탈퇴, 회원 암호 변경, 비밀번호 초기화 등이 있다. 이 경우 응용 서비스는 보통 다음의 두 가지 방법중 하나로 구현한다.

  • 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
  • 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기

 

한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
  • 한 도메인과 관련된 기능을 구현한 코드가 한곳에 위치하므로 동일 로직에 대한 코드 중복을 제거할 수 있다.
  • 한 서비스의 클래스의 크기가 커진다.
  • 코드의 크기가 커지면 연관성이 적은 코드가 한 클래스에 함께할 가능성이 높아져, 결과적으로 관련없는 코드가 뒤섞여 코드를 이해하는데 방해가 된다.
  • 한 클래스에 코드가 모이기 시작하면 엄연히 분리하는 것이 좋은 상황임에도 습관적으로 기존 클래스에 억지로 끼워넣게 된다.

 

구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
  • 클래스의 개수가 많아지지만 코드 품질을 일정 수준으로 유지하는데 도움이 된다.
  • 각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다.
  • 각 기능마다 동일한 로직을 구현할 경우 여러 클래스에 중복해서 동일한 코드를 구현할 가능성이 있다 -> 이경우 별도 클래스에 로직을 구현해 코드가 중복되는 것을 방지할 수 있음.

 

 

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

응용 서비스를 구현할 때 인터페이스가 필요할까?

 

인터페이스가 필요한 몇가지 상황
  • 구현 클래스가 여러개이고, 런타임에 구현 객체를 교체해야 할 경우 -> 응용 서비스는 위와 같은 경우가 거의 없음
  • 테스트 주고 개발(TDD)를 즐길 경우, 미리 응용 서비스를 구현할 수 없으므로 응용 서비스의 인터페이스 필요

 

 

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

응용 서비스가 제공하는 메서드는 도메인을 이용해서 사용자가 요구한 기능을 실행하는 데 필요한 값을 파라미터로 전달받아야 한다. 이때 필요한 값을 개별 파라미터로 받을 수도 있고, 별도의 데이터 클래스를 만들어 전달 받을 수도 있다.

 

응용 서비스의 결과를 표현 영역에서 사용해야 하면 응용 서비스 메서드의 결과로 필요한 데이터를 리턴한다. 이때 애그리거트 자체를 리턴하는 것은 기능 실행 로직을 응용 서비스와 표현 영역에 분산시켜 코드의 응집도를 낮추는 원인이 되므로, 표현 영역에서 필요한 데이터만 리턴하는 것이 좋다.

 

 

 

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

응용 서비스의 파라미터 타입을 결정할 때 주의할 점은 표현 영역과 관련된 타입을 사용하면 안된다는 점이다.예를 들어 HttpServletRequest HttpSession을 응용 서비스에 파라미터로 전달하면 안된다.

 

응용 서비스에서 표현 영역에 대한 의존이 발생하면 응용 서비스만 단독으로 테스트하기 어려워지고, 표현 영역의 구현이 변경되면 응용 서비스의 구현도 함께 변경해야 하는 문제가 발생한다. 이 두 문제보다 더 심각한 것응 응용 서비스가 표현 영역의 역할까지 대신하는 상황이 벌어질 수도 있다.

 

 

 

3.5 트랜잭션 처리

트랜잭션을 관리하는 것은 응용 서비스의 중요한 역할이다. 스프링과 같은 프레임워크가 제공하는 트랜잭션 관리 기능을 이용하면 쉽게 트랜잭션을 처리할 수 있다. @Transactional이 적용된 메서드가 RuntimeExcepton을 발생시키면 트랜잭션을 롤백하고 그렇지 않으면 커밋하므로 이 규칙에 따라 코드를 작성하면 트랜잭션 처리 코드를 간결하게 유지할 수 있다.

 

 

 

 

 

4. 표현 영역

표현 영역의 책임은 크게 다음과 같다.

  • 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다.
  • 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
  • 사용자의 세션을 관리한다.

 

 

 

5. 값 검증

값 검증은 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다. 원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리한다. 그러나 필수값과 형식, 범위 등을 표현 영역에서 검사하면 실직적으로 응용 서비스는 ID 중복 여부와 같은 논리적 오류만 검사하면 된다.

 

 

 

 

6. 권한 검사

권한 검사는 보통 다은 세곳에서 수행할 수 있다.

  • 표현 영역
  • 응용 서비스
  • 도메인

 

표현영역

인증된 사용자인지 아닌지 검사한다. 대표적으로 회원 정보 변경 기능이다. 회원 정보 변경과 관련된 URL은 인증된 사용자만 접근해야 한다.

 

응용영역

URL만으로 접근 제어를 할 수 없는 경우 응용 서비스의 메서드 단위로 권한 검사를 수행해야 한다. 이것이 꼭 응용 서비스의 코드에서 직접 권한 검사를 해야한다는 것은 아니고, 예를들어 스프링 시큐리티는 AOP를 활용해서 @PreAuthorize를 이용해 서비스 메서드에 대한 권한 검사를 할 수 있다.

 

 

 

도메인

게시글 삭제와 같은 경우 본인 또는 관리자만 가능하다. 게시글 작성자가 본인인지 확인하려면 게시글 애그리거트를 먼저 로딩해야한다. 이럴 경우 응용 서비스의 메서드 수준에서 권한 검사를 할 수 없기 때문에 도메인에 직접 권한 검사 로직을 구현해야 한다.

 

 

 

 

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

서비스에서 조회 전용 기능을 사용하면 추가적인 로직이 없을 뿐더러 단일 쿼리만 실행하기 때문에 트랜잭션이 필요하지도 않다. 이럴 경우 굳이 서비스를 만들 필요 없이 표현영역에서 바로 조회 전용 기능을 사용해도 된다.

728x90
반응형

댓글