Ordinary
About

만들면서 배우는 클린 아키텍처 (4 ~ 6)

profileordilov / 2022. 3. 3

유즈케이스 구현하기

유즈케이스 둘러보기

유즈케이스가 하는 역할은 다음 단계를 거칩니다.

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

여기서 입력 부분에서 입력 유효성 검증 과정은 거치지 않습니다. 유즈케이스가 검증하는 부분은 비즈니스 규칙만 채임지며 입력 유효성은 다른 곳에서 책임을 집니다.

입력 유효성 검증

입력 유효성 검증을 어댑터에게 맡기게 되면 모든 어댑터가 검증했는지 확신할 수 없습니다. 그렇다고 유즈케이스에서 하기엔 유즈케이스의 책임이 커지기 때문에 다른 방식이 필요합니다. 바로 입력 모델 자체에서 검증을 수행합니다. 즉 입력 모델 생성 시부터 유효성을 검증해 유효하지 않은 경우 객체 생성을 막습니다. 입력 모델도 API의 일부이기 때문에 인커밍 포트에 포함하기는 하지만 유즈케이스와 구분됩니다.

생성자의 힘

입력 모델은 생성자에 많은 책임을 지게 되는데 불변 객체를 만들기 위해 각 인자는 속성 값으로 채워지게 됩니다. 이 과정에서 유효하지 않은 상태의 객체는 만들 수 없게 됩니다. 이렇게 파라미터가 많다보면 빌더 패턴을 사용해 객체를 만들 생각을 할 수 있습니다. 빌더 패턴을 사용했을 때의 단점은 필드를 추가하거나 했을 때 유효하지 않은 상태의 객체를 만든다면 컴파일 에러가 나지 않습니다. 긴 파라미터이더라도 생성자를 사용한다면 컴파일 에러를 쉽게 찾고 IDE의 도움으로 각 요소의 파라미터가 무엇인지 찾기 쉽습니다.

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

하나의 서비스가 아닌 여러 유즈케이스로 나누다보면 입력 모델이 비슷한 경우 같이 사용하고 싶을 때가 있습니다. 하지만 보통 유즈케이스가 다른 경우 다른 부분이 하나 이상 있기 마련입니다. 등록하는 부분과 업데이트하기의 경우 ID의 유무가 차이가 됩니다.

이런 경우를 점점 허용할수록 불변 객체에 null 값을 허용한 상태가 됩니다. 뿐만 아니라 입력 유효성 검증 때도 유즈케이스 별로 구분하는 로직이 따로 필요하게 됩니다. 따라서 비슷해서 중복처럼 보이더라도 유즈케이스별로 구분해서 만드는 것이 미래의 유지보수에 도움이 됩니다.

비즈니스 규칙 검증하기

비즈니스 규칙은 도메인 모델 현재 상태와 관련된 검증입니다. 입력 유효성은 구문상의 유효성이라고 한다면 비즈니스 규칙은 의미적인 유효성 검증을 의미합니다.

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

엔티티 내에서 많은 도메인 로직을 처리하는 경우 풍부한 도메인 모델이라고 합니다. 반대로 엔티티는 상태만 표현하고 도메인 로직을 가지고 있지 않은 경우 빈약한 도메인 모델이라고 합니다. 대신에 도메인 로직들은 유즈케이스 클래스에 구현되어 있는데 필요에 따라 구분해서 사용하면 됩니다.

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

입력과 비슷하게 출력도 가능하면 각 유스케이스에 맞게 구체적일수록 좋습니다. 출력은 꼭 필요한 데이터만 들고 있는 것이 좋습니다. 물론 상황에 따라서 업데이트된 전체 데이터를 필요로 할지도 모르고 현재 결정되지 않았다면 가능한 적게 반환합니다. 최대한 구체적으로 유지하는 것이 중요합니다.

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

읽기 전용 작업은 유스케이스라고 표현하기 애매합니다. 도메인으로 처리하는 것이 아닌 코어 관점에서 보았을 때 단순한 데이터 쿼리라고 할 수 있습니다. 이를 구현하는 방법 중 하나는 쿼리 서비스를 따로 구현하는 것입니다. 쿼리 서비스로 명시적으로 분리하게 되면 CQRS 개념처럼 읽기와 쓰기를 분리하기 쉬워집니다.

웹 어댑터 구현하기

의존성 역전

웹 계층의 제어 흐름은 웹에서 애플리케이션 계층으로 흐르며 포트가 필요없다고 느껴질 수 있습니다. 그럼에도 추가하는 이유는 명세를 확실히 할 수 있고, 아웃고잉 포트가 필요한 경우 공유할 수 있습니다. 웹소켓처럼 실시간으로 아웃고잉이 필요한 경우 인커밍은 기존의 포트를 사용하고 아웃고잉만 추가해 사용할 수 있게 됩니다.

웹 어댑터의 책임

  1. HTTP 요청을 자바 객체로 매핑
  2. 권한 검사
  3. 입력 유효성 검증
  4. 입력을 유즈케이스의 입력 모델로 매핑
  5. 유즈케이스 호출
  6. 유즈케이스의 출력을 HTTP로 매핑
  7. HTTP 응답을 반환

입력 모델에서 유효성 검증을 하지만 웹 계층에서도 유효성 검증은 필요합니다. 웹에서 받은 입력 모델이 유스케이스 입력 모델로 변환할 수 있다는 것에 대한 검증이 필요한 것입니다.

컨트롤러 나누기

컨트롤러도 유즈케이스를 나눈 것처럼 특정 기능에 따라 나누는 것이 좋습니다. 하나의 도메인에 관련된 입력을 하나의 컨트롤러에서 다 받게 되면 컨트롤러 클래스의 코드가 비대해질 수 밖에 없습니다. 그리고 늘어난 코드만큼 코드를 파악하는데 난이도가 올라가게 됩니다. 테스트 작성 시에도 한 컨트롤러에 대한 테스트 코드가 비대해지게 됩니다. 따라서 별도의 패키지에 별도의 컨트롤러를 만들어서 작업하면 동시작업이 쉬워집니다.

영속성 어댑터 구현하기

의존성 역전

영속성 어댑터는 아웃고잉 어댑터로 애플리케이션에 의해 호출되는 어댑터입니다.

영속성 어댑터의 책임

  1. 입력을 받는다.
  2. 입력을 데이터베이스 포맷으로 매핑한다.
  3. 입력을 데이터베이스로 보낸다.
  4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
  5. 출력을 반환한다.

영속성 어댑터는 입력 모델을 데이터베이스와 통신하기 위한 포맷으로 매핑합니다. 자바에선 JPA를 일반적으로 사용하기 때문에 JPA 엔티티 객체로 매핑하게 됩니다. 핵심은 영속성 어댑터 내부를 변경하더라도 코어에는 영향이 가지 않는 점입니다. 이후 데이터베이스 응답을 출력 모델로 변환하는데 출력 모델은 애플리케이션 코어에 위치합니다.

포트 인터페이스 나누기

포트로 하나의 리포지토리에서 관리하면 서비스와 마찬가지로 너무 많은 부분에서 의존하는 넓은 포트가 됩니다. 따라서 인터페이스를 분리해 필요한 인터페이스별로 분리하는 것이 좋습니다.

영속성 어댑터 나누기

영속성 계층에서 필요에 따라 JPA 혹은 기본 SQL 어댑터를 사용하는 등 다양하게 구현해도 상관없습니다. 중요한 것은 이렇게 분리된 구현체들을 접하는 바운디드 컨텍스트가 필요합니다. 다른 바운디드에 있는 영속성 어댑터를 접근하려면 전용 인커밍 포트를 통해 접근해야 합니다.