• 육각형 아키텍처는 도메인 중심의 아키텍처에 적합하다.

유스케이스 둘러보기

  • 일반적으로 유스케이스는 다음과 같은 단계를 따른다.
    1. 입력을 받는다.
      • 입력 유효성 검증은 다른 곳에서 처리한다. (유스케이스 코드가 도메인 로직에만 신경 써야하고 입력 유효성 검증으로 오염되면 안된기 떄문)
    2. 비즈니스 규칙을 검증한다.
      • 도메인 엔티티와 유스케이스가 책임을 공유한다.
    3. 모델 상태를 조작한다.
      • 영속성 어댑터를 통해 구현된 포트로 변경된 상태를 전달해서 저장될 수 있게 한다.
    4. 출력을 반환한다.

입력 유효성 검증

  • 입력 유효성 검증은 입력 모델의 생성자 내에서 이루어진다.
  • 자바의 bean Validation API 등을 통해서 반복적인 검증을 쉽게 처리할 수 있다.
class SendMoneyCommand extends SelfValidating<SendMoneyCommand> {

  @NotNull
  private final AccountId sourceAccountId;

  @NotNull
  private final AccountId targetAccountId;

  @NotNull
  private final Money money;

  public SendMoneyCommand(
    AccountId sourceAccountId,
    AccountId targetAccountId,
    Money money) {
    this.sourceAccountId = sourceAccountId;
    this.targetAccountId = targetAccountId;
    this.money = money;
    this.validateSelf();
  }
}

유스케이스마다 다른 입력 모델

  • 각 유스케이스 전용 입력 모델은 유스케이스를 훨씬 명확하게 만들고 다른 유스케이스와의 결합도 제거해서 불필요한 부수효과가 발생하지 않게 된다.
  • 거의 비슷한 입력 모델이라고 공유하면 코드 스멜을 유발하기 쉽다.

비즈니스 규칙 검증하기

  • 입력 유효성을 검증하는 것은 구문상(syntactical) 유효성을 검증하는 것이고, 비즈니스 규칙은 유스케이스의 맥락 속에서 의미적인(semantical) 유효성을 검증하는 일이라고 할 수 있다.
  • 도메인 엔티티에서 비즈니스 규칙을 검증하고, 도메인 엔티티에서 비즈니스 규칙을 검증하기가 여의치 않다면 유스케이스 코드에서 검증할 수 있다.

풍부한 도메인 모델 vs. 빈약한 도메인 모델

  • 풍부한 도메인 모델: 애플리케이션의 코어에 있는 엔티티에서 가능한 한 많은 도메인 로직이 구현된다.
  • 빈약한 도메인 모델: 엔티티는 상태를 표현하는 필드와 값을 읽고 바꾸기 위한 getter, setter 메서드만 포함하고 어떤 도메인 로직도 가지고 있지 않다.
    • 빈약한 도메인 모델은 도메인 로직이 모두 유스케이스 클래스에 구현돼 있다.
  • 두 가지 스타일 모두 육각형 아키텍처에 접목이 가능하다.

유스케이스마다 다른 출력 모델

  • 유스케이스의 출력은 호출자에게 꼭 필요한 데이터만 들고 있어야 한다.
  • 유스케이스들 간 같은 출력 모델을 공유하게 되면 유스케이스들도 강하게 결합된다.
    • 같은 이유로 도메인 엔티티를 출력 모델로 사용하고 싶은 유혹도 견뎌야 한다.

읽기 전용 유스케이스는 어떨까?

  • 읽기 전용 작업은 유스케이스라고 언급하는 것은 조금 이상하다.
  • 애플리케이션 코어 관점에는 간단한 데이터 쿼리이기 때문에, 실제 유스케이스와 구분하기 위해 쿼리로 구분할 수 있다.
  • 읽기 전용 쿼리를 쓰기가 가능한 유스케이스와 구분하는 방식은 CQS(Command-Query Separation)나 CQRS(Command responsibility Segregation) 같은 개념과 아주 잘 맞는다.
@RequiredArgsConstructor
class GetAccountBalanceService implements GetAccountBalanceQuery {

    private final LoadAccountPort loadAccountPort;

    @Override
    public Money getAccountBalance(AccountId accountId) {
        return loadAccountPort.loadAccount(accountId, LocalDateTime.now())
                .calculateBalance();
    }
}

유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 뙬까?

  • 입출력 모델을 독립적으로 모델랑한다면 원치 않는 부수효과를 피할 수 있다.
  • 유스케이스 간에 모델을 공유하는 것보다는 더 많은 작업이 필요하지만, 분리한다면 유스케이스를 명확하게 이해할 수 있고, 장기적으로 유지보수하기도 더 쉽다.