단일책임원칙
일반적으로 알고 있는 것과 달리, “컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다”라고 정의하는 것이 더 명쾌하다.
- 컴포넌트를 변경할 이유가 오직 하나뿐이라면 다른 이유로 소프트웨어를 변경하더라도 해당 컴포넌트를 신경 쓸 필요가 없다.
의존성 역전 원칙
- 도메인 코드와 영속성 코드 간 의존성을 역전시켜 영속성 코드가 도메인 코드에 의존하고, 도메인 코드를 변경할 이유를 최소화한다.
- 도메인 계층에 리포지토리에 대한 인터페이스를 만들고, 실제 리포지토리는 영속성 계층에 구현한다.
- 모든 의존성이 도메인 코드를 향하지만, 각 계층마다 entity를 만들어야 한다.
헥사고날 아키텍처
어댑터와 포트, 애플리케이션 코어로 이루어진 아키텍처.
- 애플리케이션 코어를 호출하는 Driving Adapters와 애플리케이션 코어에 의해 호출되는 Driven Adapters로 구성
계층으로 구성하기
buckpal
- domain
- Account
- Activity
- AccountRepository
- AccountService
- persistence
- AccountRepositoryImpl
- web
- AccountController
처음 헥사고날 아키텍처를 적용하려다 보면 위와 같은 구조가 되기 십상이다.
장점
- 의존성 역전 원칙을 활용해 의존성이 domain 패키지 내 코드만을 향함
단점
- 서로 연관되지 않은 기능들끼리 묶여있어 예상치 못한 부수효과를 일으킬 수 있다.
- 애플리케이션이 어떤 유스케이스를 제공하는지 파악할 수 없다.
- 인커밍 포트와 아웃고잉 포트가 코드 속에 숨겨져 있어 어떤 아키텍처인지 파악하기 어렵다.
기능으로 구성하기
buckpal
- account
- Account
- AccountController
- AccountRepository
- AccountRepositoryImpl
- SendMoneyService
다음으로 기능을 중심으로 구조화 하는 것도 고려하게 된다.
장점
- 패키지 경계를 package-private 접근 수준과 결합하면 각 기능 사이의 불필요한 의존성을 방지할 수 있다.
단점
- 어댑터를 나타내는 패키지명이 부재하고, 인커밍 포트, 아웃고잉 포트를 확인할 수 없어 패키지 가시성이 크게 떨어진다.
- 도메인 코드와 영속성 코드 간 의존성 역전이 되었으나, package-private 접근 수준에서 도메인 코드가 실수로 영속성 코드에 의존하는 것을 방지할 수 없다.
바람직한 구조
마지막으로 아키텍처적으로 표현력 있는 패키지 구조를 고민해본다.
buckpal
- account
- adapter
- in
- web
- AccountController
- out
- persistence
- AccountPersistenceAdapter
- SpringDataAccountRepository
- domain
- Account
- Activity
- application
- SemdMoneyService
- port
- in
- SendMoneyUseCase
- out
- LoadAccountPort
- UpdateAccountStatePort
구조에서 port는 인터페이스이고 adapter는 구현체다.
장점
- 아키텍처-코드 갭이 최소화된다. 아키텍처에 코드가 효과적으로 매핑되어 가시성이 좋고 협업이 쉽다.
- 도메인에서 기능 간 패키지 분리를 통해 불필요한 기능간 의존성 발생을 방지했다.
- DDD 개념에 직접 대응시킬 수 있다.
특징
- 어댑터 패키지들은 application의 포트 인터페이스를 통해서만 호출되므로 package-private하게 유지할 수 있다. 따라서 애플리케이션 계층에서 어댑터 클래스로 향하는 우발적 의존성이 발생하지 않는다.
- application 패키지와 domain 패키지의 일부 클래스는 public이어야 한다.
- 서비스는 인커밍 포트 인터페이스에 숨겨질 수 있으므로 public일 필요가 없다.
의존성 주입의 필요성
- 도메인이 아웃고잉 어댑터에 의존성을 갖지 않기 위해서는 의존성 주입을 통해 의존성을 역전시킬 필요가 있다.