예제에서 한 계좌에서 다른 계좌로 송금하는 유스케이스를 구현!
Account 엔티티 → 실제 계좌의 현재 스냅숏 제공
Activity 엔티티 → 계좌에 대한 모든 입금과 출금을 포착
입금과 출금은 새로운 Activity를 추가하는 것에 불과하다.
출금하기 전에는 잔고를 초과하는 금액은 출금할 수 없도록 하는 비즈니스 규칙을 검사한다.
- 입력을 받는다.
- 비즈니스 규칙을 검증한다.
- 모델 상태를 조작한다.
- 출력을 반환한다.
유스케이스 코드는 도메인 로직에만 신경 써야 한다. → 입력 유효성 검증으로 오염되면 안된다! → 책임의 분리!
이때, 유스케이스는 비즈니스 규칙을 검증할 책임이 있다! → 도메인 엔티티와 이 책임을 공유하게 된다.
입력 유효성 검증은 유스케이스 클래스의 책임이 아니다! → 하지만 애플리케이션 계층의 책임이다!
호출하는 어댑터(인커밍 어댑터)가 유스케이스에 입력을 전달하기 전에 입력 유효성을 검증하면?
그럼 애플리케이션 계층 책임 OK, 근데 유스케이스 클래스의 책임이 아니면 어디서 검증하지..?
더 정확히는 입력 모델의 생성자 내에서 입력 유효성을 검증하도록 한다!
이때, 입력 모델은 사실상 유스케이스 API의 일부이기 때문에 인커밍 포트 패키지에 위치시킨다!
이러한 유효성 검증은 직접 구현하는 방법 외에도, Bean Validation API를 통해 해결할 수 있다!
입력 모델은 생성자에서 유효성 검증을 수행한다.
이때 입력 모델의 파라미터가 많다면..?
하지만 빌더의 경우, 필드를 새로 추가하는 상황에 놓여있을 때, 그것을 추가하는 것을 쉽게 잊게 된다!
필자는 빌더를 쓰는 것보다 긴 생성자를 쓰는 편이 더 좋다고 주장한다!
다른 유스케이스에서 같은 입력 모델을 공유할 경우?
불변 객체의 필드에 대해서 null을 유효한 상태로 받아들이는 것은 그 자체로 code smell(코드 악취)이다!
물론 Cost가 들지만, 각 유스케이스 전용 입력 모델은 유스케이스를 훨씬 더 명확하게 만들고 다른 유스케이스와의 결합도 제거해서 불필요한 부수효과가 발생하지 않게 한다!
비즈니스 규칙 검증은 유스케이스 로직의 일부다!
비즈니스 규칙은 애플리케이션의 핵심이기 때문이다!
비즈니스 규칙 vs 입력 유효성 검증
사실 위의 구분이 매우 모호하기에, 도메인 모델의 현재 상태 접근이라는 보다 명확한 구분을 지어놓은 듯 하다!
이로 인해, 특정 유효성 검증 로직을 코드 상의 어느 위치에 둘 지 결정하고 나중에 그것이 어디에 있는지 더 쉽게 찾는데 도움이 된다!
그럼 비즈니스 규칙은 어디에..?
입력과 비슷하게 출력도 가능하면 각 유스케이스에 맞게 구체적인 것이 좋다!
유스케이스들 간에 같은 출력 모델을 공유하게 되면 유스케이스들도 강하게 결합된다.
공유 모델은 결국 여러 이유로 점점 커지게 돼 있다!
같은 이유로 도메인 엔티티를 출력 모델로 사용하고 싶은 유혹도 견뎌야 한다!
애플리케이션 코어의 관점에서 그저 읽기 전용, 유스케이스라고 언급하기에 조금 이상한 경우, 그냥 간단한 데이터 쿼리라고 취급한다!
인커밍 전용 포트를 만들고 이를 ‘쿼리 서비스’에서 구현한다.
입출력 모델을 독립적으로 모델링 함으로써 원치 않는 부수효과를 피할 수 있다.
물론 이로 인해 많은 입출력모델 클래스 + 매핑하는 Cost가 생기게 된다!
하지만 장기적으로보면, 유스케이스를 명확하게 이해할 수 있고, 유지보수 측면에서도 매우 큰 장점을 얻을 수 있다!
꼼꼼한 입력 유효성 검증, 유스케이스별 입출력 모델은 지속 가능한 코드를 만드는 데 큰 도움이 된다!