의존성 역전

영속성 어댑터의 책임

  • 영속성 어댑터가 하는 일
    1. 입력을 받는다.
    2. 입력을 데이터베이스 포맷으로 매핑한다.
    3. 입력을 데이터베이스로 보낸다.
    4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
    5. 출력을 반환한다.
  • JPA를 사용하는 경우에는 입력 모델을 JPA 엔티티 객체로 매핑할 것이다.
    • 맥락에 따라서 입력 모델을 JPA 엔티티로 매핑하는 것이 들이는 노력에 비해 얻는 것이 많지 않은 일이될 수 있으므로 매핑하지 않는 전략도 존재한다.
  • 영속성 어댑터의 입력 모델은 애플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다.

포트 인터페이스 나누기

  • 일반적인 방법: 특정 엔티티가 필요로 하는 모든 데이터베이스 연산을 하나의 리포지토리 인터페이스에 넣어 둔다.
    • 각 서비스가 넓은 포트 인터페이스의 의존성을 갖게 되면서, 불필요한 의존성이 생긴다.
    • 맥락 안에서 필요하지 않은 메서드에 생긴 의존성은 코드를 이해하고 테스트하기 어렵게 만든다.
  • 인터페이스 분리 원칙: 클라이언트가 오로지 자신이 필요로 하는 메서드만 알면 되도록 인터페이스를 분리한다.
    • 분리할 때는 각 포트의 이름이 역할을 명확하게 잘 표현해야 한다.
    • 물론 모든 상황에 ‘포트 하나당 하나의 메서드’를 적용하지는 못한다.
      • 응집성이 높고 함꼐 사용도리 때가 많은 메서드들은 하나의 인터페이스에 묶어서 상요할 수 있다.

영속성 어댑터 나누기

  • 영속성 연산이 필요한 도메인 클래스(또는 DDD에서의 ‘애그리거트’) 하나당 하나의 영속성 어댑터를 구현하는 방식을 선택할 수 있다.
    • 또는 ORM을 이용한 어댑터와 평범한 SQL을 사용하는 어댑트로 더 많은 클래스로 나눌 수도 있다.
  • 바운디드 컨텍스트 간의 경계를 명확하게 구분하고 싶다면 각 바운디드 컨텍스트가 영속성 어댑터를 하나씩 가지고 있어야 한다.
    • 어떤 맥락이 다른 맥락에 있는 무엇인가를 필요로 한다면 전용 인커밍 포트를 통해서만 접근해야 한다.

스프링 데이터 JPA 예제

  • 입력 모델을 JPA 엔티티 객체로 매핑하기 전략을 사용할 때는
    • 도메인 모델을 JPA 엔티티 모델로, JPA 엔티티 모델을 도메인 모델로 매핑하는 로직이 필요하다.
  • 영속성 계층에서는 성능 측면에서 @ManyToOne 관계를 설정하는 것이 적절할 수 있지만, 도메인 모델에서는 데이터의 일부만 가져오기 위해서 이 관계가 반대가 되길 원할 수 있다.
    • 그러므로 영속성 측면과 타협없이 풍부한 도메인 모델을 생성하고 싶다면 도메인 모델과 영속성 모델을 매핑하는 것이 좋다.

데이터베이스 트랜잭션은 어떻게 해야 할까?

  • 영속성 어댑터는 어떤 데이터베이스 연산이 같은 유스케이스에 포함되는지 알지 못하기 때문에 언제 트랜잭션을 열고 닫을지 결정할 수 없다.
  • 이 책임은 영속성 어댑터 호출을 관장하는 서비스에 위임해야 한다.
    • 자바와 스프링에서 가장 쉬운 방법은 @Transactional 애너테이션을 애플리케이션 서비스 클래스에 붙이는 것이다.

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

  • 도메인 코드에 플러그인처럼 동작하는 영속성 어댑터를 만들면 도메인 코드가 영속성과 관련된 것으로부터 분리되어 풍부한 도메인 모델을 만들 수 있다.
  • 좁은 포트 인터페이스를 사용하면 포트마다 다른 방식으로 구현할 수 있는 유연함이 생긴다.