김동형수 개발기

만들면서 배우는 클린 아키텍처 - 04 정리 본문

책 스터디/[완료] 만들면서 배우는 클린아키텍처

만들면서 배우는 클린 아키텍처 - 04 정리

김동형수 2022. 4. 11. 03:57

유스케이스 구현하기

앞 챕터에서 설명한 내용으로 애플리케이션, 웹, 영속성 계층이 현재 아키텍처에서 아주 느슨하게 결합돼 있기 때문에 필요한 대로 도메인 코드를 자유롭게 모델링할 수 있다.

헥사고날 아키텍처는 도메인 중심의 아키텍처에 적합하기 때문에 도메인 엔티티를 만드는 것으로 시작한 후 해당 도메인 엔티티를 중심으로 유스케이스를 구현하겠다.

 


도메인 모델 구현하기

 

 

 

 

 

 

Account 엔티티는 실제 계좌의 현재 스냅숏을 제공한다. 계좌에 대한 모든 입금과 출금은 Activity 엔티티로 포착된다.

현재 회사에서 MSA환경에서 다른 모듈과 데이터 동기화할때 http통신(service mesh) / kafka 를 이용한 데이터 동기화를 하고 있는데, 이때 Activity를 이용해서 kafka에 적재하면 좋을 것 같다.

모든 입/출금 내역을 메모리에서 보관하는 방법은 현명하지 않다고 저자는 말하고 있기 때문에 ActivityWindow(전체 내역이 아닌 특정 범위 내의 내역을 볼 수 있는 인스턴스 - 예를들면 최근 1주일?)를 사용하는 것 같다.

자세하게 설명은 없지만, 저자가 의도한 바로는 일정 기간 / 횟수의 이벤트가 쌓이면 활동내역을 초기화 하면서 마지막 시점의 데이터를 엔티티에 반영하는 것 같다. 안그러면 어떤 원리로 돌아가는지 설명하기 난해한 것 같다.

Account - 상태 - 이벤트 발생 전 잔액 / 행위 - 입금, 출금

ActivityWindow - 상태 - 이벤트 내역 / 행위 - 현재 잔액


유스케이스 둘러보기

유스케이스의 단계

  1. 입력을 받는다
  2. 비즈니스 규칙을 검증한다
  3. 모델 상태를 조작한다
  4. 출력을 반환한다

인커밍 어뎁터로부터 입력을 받으면서 유효성검사를 진행할 수 있지만, 저자는 유스케이스가 도메인 로직 이외의 기능을 수행하는 걸 '오염된다'라고 표현하다. 후반부에 입력 유효성 검증 / 비즈니스 규칙 검증의 차이점에 대해서 다룬다고 한다.

내 코드는 오염이 많이 된 오물인가..? ㅠ

그러나 유즈케이스는 비즈니스 규칙을 검증할 책임이 있다. 그리고 도메인 엔티티와 이 책임을 공유한다.

책임을 공유한다는것은 변경사유가 있으면 함께 변경해야 한다는 말로 이해했는데, 앞 쪽에서 나온 하나의 변경에는 하나만 변경 한다는 내용이 안맞는 것 같다. 말이 어렵다. 책을 많이 읽어야겠다.

비즈니스 규칙을 충족하면 유스케이스는 입력을 기반으로 어떤 방법으로든 모델의 상태를 변경한다.

 

그리고 마지막으로 아웃고잉 어댑터에서 온 출력값을 유스케이스를 호출한 어댑터로 반환할 출력 객체로 변환하는 것이다.

 

'송금하기' 유스케이스에 대한 코드샘플

 

 

 

 

 

송금을 위한 인커밍 포트 인터페이스인 SendMoneyUseCase를 구현

계좌를 불러오게 할 LoadAccountPort 호출

데이터배스의 계좌 상태를 업데이트하기 위한 UpdateAccountStatePort 호출


입력 유효성 검증

입력받은 값을 어디에서 유효성 검사하는지에 대해 고민해보는 챕터이다.

나는 보통 @VALID 어노테이션을 사용해서 처리를 하고 있다.

입력 모델이 이 유효성 검사에 대해서 책임을 지면 될 것 같은 뉘양스로 저자는 말하고 있다.

샘플코드에서는 SendMoneyCommand 클래스에 해당한다.

 

이전의 방법은 생성자에서 필드의 필수값, 조건을 메서드로 검사했었는데, 'Bean Validation API' 를 사용해서 아래의 샘플 코드처럼 처리할 수 있다.

 

 

 

추상클래스인 SelfValidating에 validateSelf라는 메서드는 현재 인스턴스의 유효성검사를 진행하는 메서드이다.

 

입력모델에 있는 유효성 검증 코드를 통해 유스케이스 구현체 주위에 사실상 오류방지계층을 만들었다.

 


생성자의 힘

객체를 생성하기 위한 코드는 빌더를 사용하면 좋지만 필드의 갯수가 계속해서 늘어남에 따라 휴먼에러가 발생할 확률이 증가할 수 있다. 하지만  모든 필드를 사용하는 생성자를 사용하면 컴파일시간에 생성자에 추가되지 못한 필드들을 확인할 수 있다.

 


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

케이스 별로 필드의 필수값이 가변적으로 되는 경우 코드 냄새라고 한다. 유스케이스에 커스텀 검증 로직을 넣는 작업은 비지니스 코드를 입력 유효성 검증과 관련된 관심사로 오염시킨다.

 

이런 내용은 매핑전략으로 해결할 수 있는데 차후 8장에서 다룬다고 한다.

 

기존 개발할 때 복잡한 유효성검사는 커스텀 Validator를 사용해서 처리하고 있는데 8장에 더 나은 방법이 있을 수 있어서 기대가 된다.

 


비즈니스 규칙 검증하기

입력 유효성 검사는 유스케이스 로직에 일부가 아니지만, 비즈니스 규칙 검증은 유스케이스 로직의 일부이다.

비즈니스 규칙 검증하는 것은 도메인 모델의 현재 상태에 접근해야 하는 반면, 입력 유효성 검증은 그럴 필요가 없다는 것이다.

 

중복검사, 일정 갯수 이상 등록 못하게 막는 등 입력 자체의 규칙검사가 아닌 실제 엔티티에 접근 후 처리되어야 하는 내용은 비즈니스 규칙 검증이라고 볼 수 있을 것 같다.

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

풍부한 도메인 모델 - 엔티티에서 가능한 많은 도메인 로직이 구현된다. 엔티티들은 상태를 변경하는 메서드를 제공하고, 비즈니스 규칙에 맞는 유효한 변경만을 허용한다.

이때 유스케이스는 진입점으로만 작용하고 실제 작업을 수행하는 체계화된 도메인 엔티티 메서드 호출로 변환한다.

 

빈약한 도메인 모델 - 굉장히 얇고 상태를 표현하는 필드와 getter, setter 메서드만 존재한다.

이때 유스케이스는 도메인 로직을 포함하고 있다.


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

유스케이스들 간에 같은 출력 모델을 공유하게 되면 유스케티스들도 강하게 결합한다. 특정 유스케이스에서 필드를 추가하게 되면 다른 유스케이스에서 필요없는 필드에 대한 처리가 추가되어야한다.

높은 결합도는 소프트웨어 공학적으로 나쁜 소프트웨어가 될 가능성이 매우 높습니다.

공유 모델은 장기적으로 봤을 때 갖가지 이유로 점점 커지게 돼 있다. 단일 책임 원칙을 적용하고 모델을 분리해서 유지하는 것은 유스케이스의 결합을 제거하는데 도움이 된다.

 

도메인 엔티티를 출력모델로 사용하려는 유혹 또한 같은 이유로 견뎌야한다.


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

간단한 조회기능을 하는 내용은 유스케이스를 만들기 보단 아웃고잉 포트를 사용해서 처리한다.

쿼리를 위한 인커밍 전용 포트를 만들고 이를 '쿼리 서비스'에 구현하는 것이다.

읽기 전용 쿼리는 쓰기가 가능한 유스케이스와 코드 상에서 명확하게 구분된다. 잉런 방식은 CQS, CQRS같은 개념이다.


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

입/출력 모델을 독립적으로 만들면 우리가 원치 않아하는 부수효과들을 피할 수 있다. 공유 모델을 사용해서 유스케이스끼리 공유하는 것 보다는 많은량의 작업량이 필요하다. 각 유스케이스마다 별도의 모델을 만들어야 하고, 이 모델과 엔티티를 매핑해야 한다.

 

단점만 있지는 않다. 유스케이스 별 모델을 만들면 명확하게 이해할 수 있고, 장기적으로 유지보수하기도 더 쉽다.

 

꼼꼼한 유효성 검증, 유스케이스별 입출력 모델은 지속 가능한 코드를 만드는데 큰 도움이 된다.

Comments