계층으로 구성하기#
buckapl
|--- domain
| |----- Account
| |----- Activity
| |----- AccountRepository
| |----- AccountService
|--- persistence
| |----- AccountRepositoryImpl
|--- web
| |----- AccountController
- domain 패키지에
AccountRepository
인터페이스를 추가하고, persistence 패키지에 AccountRepositoryImpl
구현체를 둠으로써 의존성을 역전시켰다. - 위 패키지 구조가 최적이 아닌 이유
- 애플리케이션의 기능 조각이나 특성을 구분 짓는 패키지 경계가 없다.
- 애플리케이션이 어떤 유스케이스들을 제공하는지 파악할 수 없다.
- 패키지 구조를 통해서는 우리가 목표로하는 육각형 아키텍처를 파악하기 어렵다.
기능으로 구성하기#
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
|-- SendMoneyService
|-- port
|-- in
| |-- SendMoneyUseCase
|-- out
| |-- LoadAccountPort
| |-- UpdateAccountStatePort
Account
와 관련된 유스케이스를 구현한 모듈임을 나타내는 account 패키지가 있다.- 도메인 모델이 속한 domain 패키지가 있다.
- application 패키지는 도메인 모델을 둘러싼 서비스 계층을 포함한다.
SendMoneyService
는 인커밍 포트 인터페이스인 SendMoneyUseCase
를 구현한다.SendMoneyService
는 아웃고잉 포트 인터페이스이자 영속성 어댑터에 의해 구현된 LoadAccountPort
와 UpdateAccountStatePort
를 사용한다.
- adapter 패키지는 애플리케이션 계층의 인커밍 포트를 호출하는 인커밍 어댑터와 애플리케이션의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터를 포함한다.
- adapter 패키지를 package-private 수준으로 둘 수 있다.
- 모든 클래스들은 application에 있는 포트 인터페이스를 통하지 않고는 바깥에서 호출되지 않기 때문이다.
- 어댑터에서 접근 가능해야 하는 포트들은 public 이어야 한다.
- 장점
- ‘아키텍처-코드 갭’ 혹은 ‘모델-코드 갭’을 효과적으로 다룰 수 있는 강력한 요소다.
- 아키텍처-코드 갭: 아키텍처가 코드에 직접적으로 매핑될 수 없는 추상적인 개념이라는 의미. 갭이 크면 시간이 지남에 따라 코드가 목표하던 아키텍처로부터 점점 멀어지게 될 것 이다.
- 어댑터 코드를 필요할 경우 다른 구현으로 쉽게 교체가 가능하다.
- DDD 개념에 직접적으로 대응시킬 수 있다.
- account와 같은 상위 패키지는 다른 바운디드 컨텍스트와 통신할 전용 진입접과 출구(포트)를 포함하는 바운디드 컨텍스트에 해당한다.
의존성 주입의 역할#
- 아웃고잉 어댑터에 대해서는 제어 흐름의 반대 방향으로 의존성을 돌리기 위해 의존성 역전 원칙을 사용해 포트 인터페이스를 사용한다.
- 포트 인터페이스를 구현한 실제 객체를 애플리케이션 계층에 제공하기 위해서, 의존성 주입을 사용한다.
- 모든 계층에 의존성을 가진 중립적인 컴포넌트(보통, 의존성 주입 프레임워크)를 도입하고, 해당 컴포넌트가 의존성 주입을 해준다.