계층으로 구성하기

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는 아웃고잉 포트 인터페이스이자 영속성 어댑터에 의해 구현된 LoadAccountPortUpdateAccountStatePort를 사용한다.
  • adapter 패키지는 애플리케이션 계층의 인커밍 포트를 호출하는 인커밍 어댑터와 애플리케이션의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터를 포함한다.
  • adapter 패키지를 package-private 수준으로 둘 수 있다.
    • 모든 클래스들은 application에 있는 포트 인터페이스를 통하지 않고는 바깥에서 호출되지 않기 때문이다.
  • 어댑터에서 접근 가능해야 하는 포트들은 public 이어야 한다.
  • 장점
    • ‘아키텍처-코드 갭’ 혹은 ‘모델-코드 갭’을 효과적으로 다룰 수 있는 강력한 요소다.
      • 아키텍처-코드 갭: 아키텍처가 코드에 직접적으로 매핑될 수 없는 추상적인 개념이라는 의미. 갭이 크면 시간이 지남에 따라 코드가 목표하던 아키텍처로부터 점점 멀어지게 될 것 이다.
    • 어댑터 코드를 필요할 경우 다른 구현으로 쉽게 교체가 가능하다.
    • DDD 개념에 직접적으로 대응시킬 수 있다.
      • account와 같은 상위 패키지는 다른 바운디드 컨텍스트와 통신할 전용 진입접과 출구(포트)를 포함하는 바운디드 컨텍스트에 해당한다.

의존성 주입의 역할

  • 아웃고잉 어댑터에 대해서는 제어 흐름의 반대 방향으로 의존성을 돌리기 위해 의존성 역전 원칙을 사용해 포트 인터페이스를 사용한다.
  • 포트 인터페이스를 구현한 실제 객체를 애플리케이션 계층에 제공하기 위해서, 의존성 주입을 사용한다.
  • 모든 계층에 의존성을 가진 중립적인 컴포넌트(보통, 의존성 주입 프레임워크)를 도입하고, 해당 컴포넌트가 의존성 주입을 해준다.