Home 6장. 영속성 어댑터 구현하기
Post
Cancel

6장. 영속성 어댑터 구현하기

소개

image

만들면서 배우는 클린 아키텍처 책을 읽고 정리하며 소감을 적는 포스트입니다.

영속성 어댑터 구현하기

전통적인 계층형 아키텍처의 경우 영속성 계층에 의존하게 되어 데이터베이스 주도 설계가 된다고 이야기 했었다.

이번 포스트에서는 이러한 의존성을 역전시키기 위해 영속성 계층을 애플리케이션 계층의 플러그인으로 만드는 방법에 대해 살펴본다.

의존성 역전

image

코어의 서비스가 영속성 어댑터에 접근하기 위해 포트를 사용한다.

애플리케이션 서비스에서 영속성 기능을 사용하기 위해 포트 인터페이스를 호출한다. 그리고 실제 영속성 작업을 수행하고 데이터베이스와 통신할 책임을 가진 영속성 어댑터 클래스에 의해 구현된다.

포트는 사실상 애플리케이션 서비스와 영속성 코드 사이의 간접적인 계층이다. 영속성 문제에 신경 쓰지 않고 도메인 코드를 개발하기 위해, 즉 영속성 계층에 대한 코드 의존성을 없애기 위해 이러한 간접 계층을 추가하고 있다는 사실을 잊어서는 안된다.

영속성 어댑터의 책임

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

1. 입력을 받는다.

영속성 어댑터는 포트 인터페이스를 통해 입력 받는다. 입력 모델은 인터페이스가 지정한 도메인 엔티티나 특정 데이터베이스 연상 전용 객체가 된다.

2. 입력을 데이터베이스 포맷으로 매핑한다.

영속성 어댑터는 데이터베이스를 쿼리하거나 변경하는 데 사용할 수 있는 포맷으로 입력 모델을 매핑한다.

핵심은 영속성 어댑터의 입력 모델이 영속성 어댑터 내부에 있는 것이 아니라 애플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다.

3. 입력을 데이터베이스로 보낸다.

영속성 어댑터는 데이터베이스에 쿼리를 날리고 쿼리 결과를 받아온다.

4. 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.

데이터베이스 응답을 포트에 정의된 출력 모델로 매핑해서 반환한다.

5. 출력을 반환한다.

출력 모델이 영속성 어댑터가 아니라 애플리케이션 코어에 위치하는 것이 중요하다.


입출력 모델이 영속성 어댑터가 아니라 애플리케이션 코어에 있다는 점을 제외하면 책임은 전통적인 영속성 계층의 책임과 크게 다르지 않다.

포트 인터페이스 나누기

서비스를 구현하면서 생기는 의문은 데이터베이스 연산을 정의하고 있는 포트 인터페이스를 어떻게 나눌 것인가다.

아래와 같인 특정 엔티티가 필요로 하는 모든 데이터베이스 연산을 하나의 리포지토리 인터페이스에 넣어 두는 것이 일반적인 방법이다.

image

하나의 아웃고잉 포트 인터페이스에 모든 데이터베이스 연산을 모아두면 모든 서비스가 실제로는 필요하지 않은 메서드에 의존하게 된다.

하나의 리포지토리 인터페이스에 모든 것을 넣게 되면 데이터베이스 연산에 의존하는 각 서비스는 인터페이스에서 단 하나의 메서드만 사용하더라도 하나의 넓은 포트 인터페이스에 의존성을 갖게 된다.

코드에 불필요한 의존성이 생겼다는 뜻이다.

필요하지 않은 메서드에 생긴 의존성은 코드를 이해하고 테스트하기 어렵게 만든다. 테스트에서 작업하는 사람은 인터페이스 전체가 모킹 됐다고 기대하는 바람에 어떤 메서드가 모킹되었는지 찾지 못하고 에러를 보게 될 수 있다.

“필요없는 화물을 운반하는 무언가에 의존하고 있으면 예상하지 못했던 문제가 생길 수 있다.”
로버트 C. 마틴

인터페이스 분리 원칙(Interface Segregation Principle, ISP)은 이 문제의 답을 제시한다.

이 원칙은 클라이언트가 오로지 자신이 필요로 하는 메서드만 알면 되도록 넓은 인터페이스를 특화된 인터페이스로 분리해야 한다고 설명한다.

image

인터페이스 분리 원칙을 적용하면 불필요한 의존성을 제거하고 기존 의존성을 눈에 더 잘 띄게 만들 수 있다.

위와 같이 인터페이스를 나누면 각 서비스는 실제로 필요한 메서드에만 의존하게 된다. 나아가 포트의 이름이 포트의 역할을 명확하게 잘 표현하고 있다.

매우 좁은 포트를 만드는 것은 코딩을 플러그 앤드 플레이(plug-and-play) 경험으로 만든다.

영속성 어댑터 나누기

아래와 같이 영속성 연산이 필요한 도메인 클래스(또는 DDD에서의 에그리게이트) 하나당 하나의 영속성 어댑터를 구현하는 방식을 선택할 수 있다.

image

하나의 애그리게이트당 하나의 영속성 어댑터를 만들어서 여러 개의 영속성 어댑터를 만들 수도 있다.

위 그림과 같이 하면 영속성 어댑터들은 각 영속성 기능을 이용하는 도메인 경계를 따라 자동으로 나눠진다.

도메인 코드는 영속성 포트에 의해 정의된 명세를 어떤 클래스가 충족시키는지에 관심 없다는 사실을 기억해야한다.

애그리게이트당 하나의 영속성 어댑터 접근 방식 또한 나중에 여러 개의 바운디드 컨텍스(bounded context)의 영속성 요구사항을 분리하기 위한 좋은 토대가 된다.

image

바운디드 컨텍스트 간의 경계를 명확하게 구분하고 싶다면 각 바운디드 컨텍스트가 영속성 어댑터(들)을 하나씩 가지고 있어야 한다.

각 바운디드 컨텍스트는 영속성 어댑터를 하나씩 가지고 있고 바운디드 컨텍스트라는 표현은 경계를 암시한다.

account서비스가 billing 영속성 어댑터에 접근하지 않고, 반대로 billing의 서비스도 account의 영속성 어댑터에 접근하지 않는다는 의미이다.

서로 접근하려면 전용 인커밍 포트를 통해 접근해야 한다.

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

트랜잭션 경계는 어디에 위치시켜야 할까??

트랜잭션은 하나의 특정한 유스케이스에 대해서 일어나는 모든 쓰지 작업에 걸쳐 있어야 한다. 그래야 그중 하나라도 실패할 경우 다 같이 롤백 될 수 있기 때문이다.

영속성 어댑터는 어떤 데이터베이스 연산이 같은 유스케이스에 포함되는 알지 못하기 때문에 언제 트랜잭션을 열고 닫을지 결정할 수 없다.

이 책임은 영속성 어댑터 호출을 관장하는 서비스에 위임해야 한다.

This post is licensed under CC BY 4.0 by the author.

5장. 웹 어댑터 구현하기

7장. 아키텍처 요소 테스트하기