3. 코드 구성하기
코드만 보더라도 어떤 아키텍처인지 알 수 있다면 좋지 않을까? 보통 새로운 프로젝트를 시작할 때 가장 먼저 패키지 구조를 설계하게 된다.
계층으로 구성하기
기본적인 계층형으로 프로젝트를 생성한다면 아래와 같은 구조가 될 것이다.
1 | buckpal |
도메인, 웹, 영속성 계층별로 패키지를 만들었고 앞서 나왔던 의존성 역전을 사용하여 domain 패키지에 AccountRepository 인터페이스를 두고 persistence 패키지에 구현체를 둬서 의존성이 도메인을 바라보도록 구성되어있다.
다만 몇가지 단점이 보이는데
- 기능이나 특성을 구분짓는 패키지의 경계가 없다.
- 새로운 기능(ex) 사용자관리)을 추가하려면 각 계층 패키지에 UserController, UserService, User 등을 추가하게 되는데 다른 기능과 섞이게 되면 예상치 못한 부수효과가 발생할 수 있다.
- 애플리케이션이 어떤 유스케이스를 제공하는지 파악하기 어렵다.
- AccountController와 AccountService가 구체적으로 어떤 기능을 제공하는지 파악하려면 내부 구현 메서드를 살펴봐야 한다.
- 패키지만 봐서 의도하는 아키텍처를 짐작하기 어렵다.
- 육각형 아키텍처라고 추측하고 웹 어댑터와 영속성 어댑터를 찾기 위해 web, persistence 패키지를 조사해볼 순 있지만 어떤 기능이 웹어댑터에서 호출되는지, 영속성 어댑터가 도메인 계층에 어떤 기능을 제공하는지 한눈에 알 수 없다. 인커밍 포트와 아웃고잉 포트가 코드 속에 숨겨져 있다.
기능으로 구성하기
1 | buckpal |
계좌 관련 기능을 모두 account 라는 패키지에 모았고 AccountService도 책임을 좁히기 위해서 SendMoneyService로 변경하였다.
이렇게 되면 ‘송금하기’ 유스케이스를 구현한 코드는 클래스명만 봐도 바로 찾을 수 있다.
하지만 기능을 기준으로 코드를 구성하면 기반 아키텍처가 명확하게 보이지 않아서 가시성이 많이 떨어진다는 큰 단점이 있다.
아키텍처적으로 표현력 있는 패키지 구조
육각형 아키텍처에서 구조적으로 핵심적인 요소는 엔티티, 유스케이스, 인커밍/아웃고잉 포트, 인커밍/아웃고잉(혹은 주도하거나 주도되는) 어댑터이다.
1 | buckpal |
도메인 모델이 속한 domain 패키지와 도메인 모델을 둘러싼 서비스 계층을 포함하는 application패키지가 있다.
SendMoneySerivce는 인커밍 포트 인터페이스인 SendMoneyUseCase를 구현 아웃고잉 포트 인터페이스이자 영속성 어댑터에 의해 구현된 LoadAccountPort와 UpdateAccountStatePort를 사용한다.
adapter 패키지는 애플리케이션 계층의 인커밍 포트를 호출하는 인커밍 어댑터와 애플리케이션 계층의 아웃고잉 포트에 대한 구현을 제공하는 아웃고잉 어댑터를 포함한다.
- 책을 읽다가 곰곰히 생각해 봤지만
패키지의 구조가 표현력이 있긴한데 아직 익숙치 않아서 한눈에 들어오진 않는다. 다만, 팀원들과 이러한 아키텍처에 대한 논의가 충분히 되고 합의된 상태에서 구조를 잡는다면 코드와 아키텍처가 직접적으로 매핑되면서 추상적이던 아키텍처가 좀 더 구체적으로 파악이 가능해진거 같기도 하다.