Hexagonal Architecture
Ports & Adapters / Hexagonal Architecture 로 불린다.
목적
- Interafce 의 종류와 관계없이 동등하게 사용될 수 있도록
- 최종적으로 구성될 런타임 환경과 분리되어서 개발되고 테스트 가능하도록
application 의 business logic 을 외부환경과 격리하는 것
Port / Adapter
- 포트 (Port)
- Hexagonal 은 applciation 이 중심이다. 그러므로 상호작용에 대한 명세는 application 에서 제공한다.
- Driving Port
- 애플리케이션을 사용하는 측에 제공하는 인터페이스
- Driven Port
- 애플리케이션이 사용하는 외부 서비스를 위한 인터페이스
- 어댑터 (Adapter)
- Port 에 부합하도록 구현한 구현체를 Adapter 라고 한다.
- Controller, Repository 구현체
Application Core
- 코어에서는 외부 의존성이 없다.
- 인터페이스 명세가 해당 레이어에 존재하고, 상호작용은 interface 를 거쳐서 하게된다. 결국 다른 layer 에 대한 의존성이 없다.
- 다만, application core 에서도 도메인과 application 레이어를 분리할 수 있는데 이때는 Core 내부의 application 레이어는 domain 레이어를 참조한다.
Code Structure
위 내용을 바탕으로 대략의 프로젝트 구조를 만들어 보면 아래와 같이 만들어 볼 수 있다.
project-root └── user ├── adapter │ ├── driving │ │ └── web │ │ └── UserController │ └── driven │ └── persistence │ ├── UserPersistenceAdapter │ └── UserTypeOrmRepository ├── domain │ └── User └── application ├── RegisterUserService └── port ├── in │ └── RegisterUserUseCase └── out └── CreateUserPort
보통 일반적인 예제는 위와같다. 하지만 여기서는 한가지 문제가 있다.
port out 쪽이 문제가 될 것으로 예상되는데, createUserPort 를 다른 Service 에서 재사용해야 하는 경우가 늘어나면 추후에는 각 application layer 간 참조가 물고 물리면서 상호참조가 일어날 수 있을 것 같다.
이를 방지할 수 있는 방법으로 domain 모델에 대한 persistence port 들은 domain 쪽으로 몰아둘 수 있다.
project-root └── user ├── adapter │ ├── driving │ │ └── web │ │ └── UserController │ └── driven │ └── persistence │ ├── UserPersistenceAdapter │ └── UserTypeOrmRepository ├── domain │ ├── User │ └── port │ └── out │ └── CreateUserPort └── application ├── RegisterUserService └── port └── in └── RegisterUserUseCase
이렇게 되면 persistence adapter 에서는 domain 만을 참조하고, web adapter 에서는 applcation layer 만을 참조할 수 있게된다.
References
- Alistair Cockburn 원문
- Juan Manuel Garrido 의 Hexagonal 관련된 상세한 posting