[Clean Architecture] 육각형 아키텍처 - 영속성 계층 구현
이전 포스팅에서 웹 계층인 Controller를 어떻게 설계하면 좋을지를 다뤘었다.
https://devkingdom.tistory.com/343
오늘은 코어를 중심으로 (그림상) 오른쪽에 있는 영속성 계층을 어떻게 설계하면 좋을지에 대해 조금 다뤄보도록 하겠다.
보통 MVC 관련 프로젝트르 하다보면 하나의 딜레마에 빠지게 된다.
바로 데이터베이스 주도 설계를 하게 되는 것이다. 이는 일반적으로 사용하는 계층형 아키텍처의 고질적인 문제점 중 하나이다.
오늘은 육각형아키텍처에서 의존성 역전을 통해 영속성 계층을 어플리케이션 계층의 플러그인 형태로 만드는 방법을 포스팅할 예정이다.
영속성 계층
영속성 계층이란?
영속성 계층을 구현하는데 있어 가장 핵심적인 것은 바로 의존성 역전이다.
아래 그림을 보자.
위의 구조는 의존성 역전을 제대로 보여준다.
어플리케이션 서비스에서 영속성의 기능들을 사용하기 위해서 포트 인터페이스를 호출해준다. 여기서 이 포트는 데이터베이스와의 통신을 책임지는 어댑터에 의해 구현이 되고, 어플리케이션이 호출하게 만든다.
만약에 영속성 부분의 코드가 수정되거나 리팩토링이 진행되더라도 이 구조라면 코어 쪽의 코드를 변경할 필요가 전혀 없다.
포트가 서비스와 영속성 계층 사이에서 서비스의 영속성에 대한 의존성을 없애주기 때문에
어댑터가 어플리케이션을 호출하지 않게 되고 결국 데이터베이스에 의존하지 않는 설계가 가능하게 된다.
이러한 영속성 어탭터를 '주도되는 어댑터' 혹은 '아웃고잉 어댑터'라 부른다.
영속성 어댑터의 책임
영속성 어댑터는 일반적으로 아래의 역할을 수행한다.
1. 입력을 받음
2. 받은 입력을 데이터베이스에 맞게 매핑
3. 매핑된 입력을 데이터베이스로 전송
4. 데이터베이스의 출력을 어플리케이션에 맞게 매핑
5. 출력을 리턴
영속성 어댑터의 주된 특징은 포트라는 인터페이스를 통해 입력을 받는다. 입력되는 모델은 인터페이스가 지정한 도메인 엔티티나 데이터 베이스 연산을 위한 전용 객체로 입력된다.
그 이후 데이터베이스의 다양한 쿼리를 처리하는 사용할 수 있는 데이터베이스 맞춤형 입력 모델을 매핑한다. 보통 자바진영에서는 JPA를 사용하고 , C#에서는 EF Core를 사용한다.
여기서 입력된 모델을 영속성 프레임워크의 엔티티로 매핑하는게 어느정도 비용이 들어가는 일이라는 것은 알아두자.
여기서 ORM 프레임워크에 대한 것만 말을 했지만 사실 JDBC Template을 이용해 연동하는 경우나 MyBatis를 이용해서 데이터베이스를 연동하는 경우 등 어떠한 방식이 사용되어도 코어와 영속성은 서로 의존하지 않는다.
영속성 어탭터의 입력모델이 어플리케이션 코어에 있기때문에 무슨 방식을 사용하는지는 코어는 관심이 없다.
마지막으로 영속성 어댑터는 데이터 베이스의 응답을 정의한 출력 모델로 매핑 후 반호나한다. 이때 출력모델의 위치는 어플리케이션 코어에 있어야한다는 것을 잘기억하자. 출력모델도 어플리케이션 코어에 존재하게 함으로 써 의존성이 입력 부터 출력까지 아예 없게 만드는 것이다.
인터페이스 분리 원칙
서비스를 개발하다보면 아래 그림처럼 특정 엔티티가 필요로 하는 데이터베이스 연산을 한군데 모아놓은 레퍼지토리가 만들어 지게 된다.
이렇게 되면 이 인터페이스에 불필요한 의존성이 발생하게 된다.
예를 들어 AService는 단위 테스트 작성시, BService와 공동으로 XRepository를 사용하고 있기때문에 XRepository의 어떤 메서드를 모킹해야할지를 판단하기가 어렵다. 그리고 모킹이 모든 메서드에 대해 다되어있다고 다른 개발자가 판단해버리면 이는 또 테스트과정에서 에러로 이어지게 될 수도 있다.
이 과정에서 인터페이스 분리 원칙을 이용한다.
이를 통해 각서비스가 실제로 필요한 메서드에만 의존하도록 만들게 된다. 이때 중요한 것은 포트의 이름이 포트의 역할이 무엇인지 잘 알 수 있도록 정의해줘야한다.
이렇게만 해두면 실제로 개발할할때 필요한 포트를 그냥 가져다가 블럭처럼 꽃아서 (plug-and-play) 사용하면 된다.
각 포트의 메서드들은 응집도를 고려하여 적적할게 묶어서 포트로 제공해주도록 하자.
영속성 어댑터 나누기
영속성 어댑터도 나눠줘야할 필요가 있다.
나누는 방법은 영속성 기능을 이용하는 도메인의 경계(바운디드 컨텍스트)를 따라 나눠주면된다. DDD 관점에서 이를 '애그리거트당 하나의 영속성 어댑트'를 만들어 준다고 표현한다.
아래그림을 보면 각 바운디드 컨텍스트가 영속성 어댑터를 하나씩 가지게 하였다.
cash 관련 서비스가 billing 의 영속성 어댑터에 접근하지 않고, 마찬가지로 billing 관련 서비스는 cash의 영속성 어댑터에 접근하지 않는다. 만약 정말 접근해야할 필요가 있게되면 전용 인커밍 포트를 만들어 접근하게 해야한다.
Ref.
톰 홈버그, 『만들면서 배우는 클린 아키텍처』, 위키북스(2021), p63~70