
![업로드중..]()



헥사고날 아키텍처란?
- "포트와 어댑터 아키텍처" 라고도 한다.
- 이 아키텍처의 주요 목표는 "어플리케이션의 비즈니스 로직을 외부 세계로부터 분리시켜 유연하고 테스트하기 쉬운 구조를 만들자" 다.
- 이를 위해 핵심 비즈니스 로직은 중앙의 도메인 영역에 위치하며, 입력과 출력을 처리하는 포트와 어댑터를 통해 외부와 소통한다.
- 내부 → 도메인, 외부 → 인프라
- 내부 : 순수한 비즈니스 로직을 표현, 캡슐화된 영역, 기능적 요구사항에 따라 먼저 설계
- 외부 : 내부 영역에서 기술을 분리해서 구성한 영역, 내부 영역 설계 이후 설계
왜 헥사고날 아키텍처를 쓰는가?
✅ 관심사의 분리
- 외부와의 연결에 문제가 생기면? → 어댑터 보면 됨
- 인터페이스는? → 포트 보면 됨
- 처리 중간에 이벤트 브릿지에 이벤트를 보내고 싶다면? → 서비스 보면 됨
- 비즈니스 로직이 제대로 동작하지 않으면? → 도메인 모델 보면 됨
✅ 좀 더 쉬운 테스트
- 인바운드 어댑터는 서비스 내부 구현을 Mocking 할 필요 없이, 외부에서 들어온 데이터가 잘 가공되어 서비스에 전달되는지만 확인한다.
- 서비스는 어댑터나 도메인 객체가 정상적으로 동작하는지 확인할 필요 없이, 순서대로 오케스트레이션 잘 되는지만 확인한다. 이 때 모든 어댑터는 포트를 기반으로 하기 때문에, 쉽게 FakeAdapter 를 작성해서 서비스를 테스트 해볼 수 있다.
- 아웃바운드 어댑터는 kafka, redis 등 외부 서비스를 mocking 해서 파라미터값이 의도대로 전달되는지를 확인한다.
- 도메인 로직은 모든 비즈니스 로직에 대한 테스트를 작성하게 되지만, 의존성이 없기 때문에 mocking 할 필요가 거의 없다. (있더라도 포트를 통해 아웃바운드 어댑터를 쉽게 모킹할 수 있다)
- 마지막으로 위 모든 과정에서 포트는 검증이 되므로, 포트는 따로 테스트할 필요 없다.
데이터 흐름
- 사용자의 요청은 왼쪽에서 → 오른쪽으로 처리된다.
- 좌측 어댑터를 통해 사용자의 요청을 받아서 어플리케이션 서비스에 전달한다.
- 서비스와 어댑터는 포트를 인터페이스로 사용하여 통신한다. (어댑터 디자인 패턴 느낌으로)
- 어플리케이션 서비스는 요청을 도메인 모델로 전달하고, 도메인 모델은 비즈니스 요청을 처리한 후 우측 어댑터를 통해 외부의 데이터를 가져오거나 처리된 데이터를 외부로 저장한다.
- 필요한 경우 어플리케이션 서비스는 도메인 모델의 처리결과를 전달받아 다시 사용자에게 반환한다.
포트와 어댑터
포트
- 포트는 단순히 인터페이스다. 이 포트를 구현하여 실제 동작을 구현하게 된다.
- 어플리케이션 입장에서 consumer 입니다.
- 어플리케이션에서 나가거나 들어오는 endpoint 라고 볼 수 있다.
- 포트는 내부 비즈니스 영역을 외부 영역에 노출한 API 이고,
인바운드 포트, 아웃바운드 포트로 구분한다.
- 인바운드 포트 : 내부 영역 사용을 위해 노출된 API
- 아웃바운드 포트 : 내부 영역이 외부 영역을 사용하기 위한 API
어댑터
왼쪽의 어댑터는 사용자의 요청을 받아들일 때 사용, 오른쪽의 어댑터는 도메인 모델의 처리에 사용
어플리케이션 서비스 / 유스케이스
- 어플리케이션 서비스는 어댑터들과 도메인모델의 오케스트레이션을 담당하게 된다.
헥사고날 아키텍처의 장점
-
도메인 비즈니스 모델에 집중할 수 있다.
: DIP를 통해 의존성이 도메인에서 밖으로 나가는 부분이 없어서 외부 요소를 신경쓰며 개발 할 필요가 없기 때문이다.
-
모듈 일부를 배포하는게 용이하다.
: 기술과 실제 비즈니스 로직을 분리하고, 각 도메인 별 비즈니스 로직을 분리를 해서 느슨한 결합을 가져갈 수 있기 때문이다.
-
기능 확장이 용이하다.
: 원하는 기능에 대한 포트와 해당 포트를 사용할 어댑터를 추가해주면 된다.
-
쉬운 테스트 구성
: 모든 외부 기술들은 포트를 통해 비즈니스 로직과 연결되기 때문에, 본인의 역할을 수행하기 위해 필요한 포트만 사용해서 mock 어댑터를 통해 테스트를 쉽게 수행할 수 있다.
또한 내부 비즈니스 로직 테스트할 때 외부에 의존성이 없기 때문에 모킹 필요성도 적어진다.
-
개발 비용 감소
: 모든 의존성이 도메인을 향하기 때문에 계층 간 의존성이 낮아지고 유연해진다. 때문에 요구사항에 빠르게 대처할 수 있고, 테스트도 쉽게 적용할 수 있다.
-
관심사 분리의 장점 (SoC)
: 외부와의 연결에 문제가 생기면 어댑터를 확인하면 될 것이고,
인터페이스의 정의를 변경하고자 한다면 포트를,
비즈니스 로직이 제대로 동작하지 않는다면 도메인 로직만 확인하면
되기 때문에 책임이 완벽하게 분리되어 있어 코드의 이해와 수정이 용이하고 변화에 빠르게 대응할 수 있으며 쉬운 테스트도 가능하게 한다.
헥사고날 아키텍처의 단점
-
코드가 많아진다. (구현 복잡성)
: 도메인 계층이 영속성, UI 같은 외부계층과 철저히 분리되어야 하기 때문에 엔티티에 대한 모델을 각 계층에서 유지보수 해야 한다.
ex) 도메인 계층은 영속성 계층을 모르기 때문에, 두 계층에서 각각 엔티티를 만들어주어야 하고, 도메인 계층과 영속성 계층 간 데이터를 주고받을 때 두 엔티티를 서로 매핑하는 과정이 생기게 된다.
-
불필요한 오버헤드. (초반 개발 시간의 증가)
아키텍처를 도입하기 전 포트, 어댑터 등 알아야하는 개념이 생기고,
아키텍처를 구현하기 위해 포트(인터페이스)를 생성해야 하고 도메인 모델의 여러 표현 사이를 매핑할 객체를 만들어야 한다.
3계층 아키텍처와의 차이점
- 가장 대중적으로 사용하는 3계층 아키텍처는 비즈니스 로직, 데이터 액세스, 프레젠테이션 계층으로 구성된다.
- 이와 달리 헥사고날 아키텍처는 비즈니스 로직에 중점을 두고 외부와 격리되어 있어, 더욱 유연하고 테스트하기 쉽다.
예시 1
3계층 아키텍처
- UserService : 비즈니스 로직
- UserRepository : 인터페이스를 통해 데이터 액세스 계층과 소통
헥사고날 아키텍처
- CreateUserUseCase : 인터페이스를 통해 비즈니스 로직을 정의
- CreateUserUseCaseImpl : 클래스에서 구현
- UserRepositoryAdapter : UserRepository 인터페이스를 구현하며, 외부와의 소통을 담당
예시 2
주로 카프카 같은 외부 시스템을 연결하는 예제를 통해 헥사고날 아키텍처의 장점을 알 수 있다.
3계층 아키텍처 with Kafka 적용
- KafkaProducer : 카프카에 사용자 정보 전송 로직
- UserService : 카프카와 직접 연결되어 있어, 카프카에 대한 의존성을 가지게 된다.
헥사고날 아키텍처 with Kafka 적용
- OutputPort : 이 인터페이스를 통해 외부 시스템과의 의존성을 분리한다.
- KafkaAdapter : 이 클래스에서 카프카와 연결하는 로직을 처리한다. 내부에 KafkaProducer 를 인자로 가지고 있다.
- CreateUserUseCaseImpl : OutputPort 인터페이스를 통해 메세지를 전송한다.
→ 만약 KafkaProducer를 카프카 말고 다른 메세징 시스템인 RabbitMQ로 교체하려면 3계층 아키텍처에서는 UserService 를 수정해야 한다. 헥사고날 아키텍처에서는 OutputPort 인터페이스를 구현한 새로운 어댑터를 만들어서 주입하면 된다.
결론
→ 헥사고날 아키텍처는 비즈니스 로직과 외부 요소를 격리시키기 때문에 유연성과 테스트 용이성이 향상되는 장점이 있다.