본문 바로가기
Back-End/도메인 주도 개발 시작하기

[DDD] 도메인 주도 개발 시작하기 Ch6

by SoyeonCha 2024. 3. 25.

6.1 표현 영역과 응용 영역

∘  표현 영역 : 사용자의 요청을 해석

  ∘  사용자가 실행하고 싶은 기능을 판별하고 해당 기능의 응용 서비스를 실행

∘  응용 영역 : 사용자가 실행하고 싶은 서비스가 위치한 영역

∘  사용자 → 표현 영역 → 응용 영역

∘  사용자의 요청 데이터를 바탕으로 응용 서비스가 원하는 형식의 객체를 생성하는 과정 필요

∘  사용자 ← 표현 영역 ← 응용 영역

∘  응용 서비스의 실행한 결과를 표현 영역에서 사용자에게 알맞은 형식으로 답해줌

∘  응용 서비스는 표현 영역에 의존하지 않음

 

6.2 응용 서비스의 역할

∘  응용서비스는 리포지터리에서 도메인 객체를 가져와서 사용자의 요청을 처리

public Result doSomeFunc(SomeReq req){
		SomeAgg agg = someAggRepository.findById(req.getId()); //리포지터리에서 애그리거트 구하기
		agg.doFunc(req.getValue()); //애그리거트의 도메인 기능을 실행
		return createSuccessResult(agg); //결과 리턴
}

∘  새로운 애그리거트를 생성하는 응용 서비스

public Result doSomeCreation(CreateSomeReq req){
		validate(req); //데이터가 유효한지
		SomeAgg newAgg = createSome(req); //애그리거트 생성
		someAggRepository.save(newAgg); //리포지터리에 애그리거트 저장
		return createSuccessResult(newAgg); //결과 리턴
}

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

∘  응용 서비스는 트랜잭션 범위에서 실행되어야 함

  ∘  변경 사항을 DB에 반영하는 도중에 문제가 발생하는 것에 대비

 

6.2.1 도메인 로직 넣지 않기

∘  일부 도메인 로직을 응용 서비스에서 구현할 때 발생하는 문제

  1. 코드의 응집도가 떨어짐
  2. 여러 응용 서비스에서 동일한 도메인 로직을 중복해서 작성할 가능성 높아짐
public void changePassword(String oldPw, String newPw){
		if(!matchPassword(oldPw)) throw new BadPasswordException();
		setPassword(newPw);
}

public boolean matchPassword(String pwd){
		return passwordEncoder.matches(pwd);
}

private void setPassword(String newPw){
		if (isEmpty(newPw)) throw new IllegalArgumentException("no new password");
		this.password = newPw;
}
public class ChangePasswordService {
		public void changePassword(String memberId, String oldPw, String newPw){
		Member member = memberRepository.findById(memberId);
		checkMemberExists(member);

		if(!passwordEncoder.matches(oldPw, member.getPassword(){
				throw new BadPasswordException();
		}
		member.setPassword(newPw);
}

6.3 응용 서비스의 구현

6.3.1 응용 서비스의 크기

응용 서비스 구현 방식

1. 한 서비스 클래스에 도메인의 모든 기능 구현

  ∘  각 기능에서 동일한 로직에 대한 코드가 중복되는 것을 막을 수 있음

  ∘  연관성이 적은 코드끼리 붙어 있게 되어 코드 이해가 어려움

 

2. 기능별로 서비스 클래스를 따로 구현

   ∘  클래스의 개수 많아짐

   ∘  코드 품질 일정 수준 유지 가능

   ∘  클래스끼리 서로의 코드에 영향을 주지 않음

   ∘  로직의 중복 구현 문제는 공통 로직을 제공하는 메서드를 만들어서 해결할 수 있음

 

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

  • 인터페이스가 필요한지 명확하지 않은데 인터페이스를 작성하는 것은 좋지 않은 선택

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

  • 응용 서비스의 메서드는 도메인을 이용해서 기능을 실행하는 데 필요한 값을 파라미터로 전달받음
    • 개별 파라미터로 전달받음
    public class ChangePasswordService{
    		public void changePassword(String memberId, String curPw, StringnewPw){
    			...
    }
    
    • 별도 데이터 클래스를 이용해 전달받음 (파라미터 개수가 2개 이상이면 이 방법이 좋음)
    public class ChangePasswordRequest{
    		private String memberId;
    		private String currentPassword;
    		private String newPassword;
    		 ...
    }
    
  • 표현 영역에서 응용 서비스의 결과를 필요로 하면 응용 서비스가 필요한 데이터를 리턴하도록 함 ex. 주문 번호
  • 애그리거트 객체 자체를 리턴할 수도 있음 → 도메인 로직을 응용 서비스, 표현 영역에서 실행할 수 있게 되어 코드 응집도 낮아짐
  • 응용 서비스가 표현 영역이 필요로 하는 데이터만 리턴하도록 하는 것이 좋음

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

  • 응용 서비스의 파라미터 타입으로 표현 영역과 관련된 타입을 사용하면 안 됨
  • 표현 영역과 관련된 타입을 사용할 경우 응용 서비스가 표현 영역의 역할을 대신하는 문제가 발생할 수 있음

6.3.5 트랜잭션 처리

  • 프레임워크가 제공하는 트랜잭션 기능을 이용하면 메서드가 정상적으로 실행되면 커밋하고 exception이 발생하면 rollback하게 됨
  • 프레임워크를 이용하면 간결한 코드로 트랜잭션 관리 가능

6.4 표현 영역

  • 표현 영역의 책임
    1. 사용자가 시스템을 사용할 수 있는 화면을 제공하고 제어한다.
    2. 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
    3. 사용자의 세션을 관리한다.

6.5 값 검증

  • 원칙적으로 모든 값 검증은 응용 서비스에서 처리함
  • 여러 개의 파라미터 값이 올바른지 검사하는 방법
    • 응용 영역에서 각 값이 유효한지 따지는 익셉션을 각각 작성
    • → 사용자가 첫 번째 에러만 확인하게 되고 다른 값들이 유효한지 아는 데에 번거로움이 있음
    • 각 값마다 쓴 익셉션 코드를 하나의 익셉션 코드로 모음
    • → 익셉션에서 에러 목록을 가져와서 표현 영역에서 사용
    • 표현 영역은 필수 값, 값의 형식, 범위 등을 검증하고 응용 서비스는 논리적 오류를 검증

6.6 권한 검사

  • 권한 검사 : ‘사용자 U가 기능 F를 실행할 수 있는지’ 확인하는 것
  • 시스템마다 권한의 복잡도가 다름
  • 권한 검사를 수행할 수 있는 영역 : 표현 영역, 응용 서비스, 도메인
    • 표현 영역 : 인증된 사용자인지 아닌지
    • 응용 서비스 : URL만으로 접근 제어가 불가할 때 메서드 단위로 검사
    • 도메인 : 객체 단위의 권한 검사 로직을 직접 구현하거나 프레임워크 이용

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

  • 단일 쿼리만 실행하는 조회라면 서비스 없이 표현 영역에서 조회 전용 기능을 사용해도 됨
  • 사용자가 요청한 기능의 실행에 응용 서비스가 기여하는 게 없으면 표현 영역에서 응용 서비스 없이 조회 기능으로 접근해도 됨