헥사고날 아키텍처란?
요즘 어떤 회사를 봐도 MSA에 대한 요구사항들이 있다. MSA 구조로 개발을 하다보면 자주 접하는 헥사고날 아키텍처에 대해 내 생각을 정리해보려고 한다. 헥사고날 아키텍처(Hexagonal Architecture, 또는 포트와 어댑터 아키텍처)는 비즈니스 로직을 외부 환경으로부터 분리하여 유연하고 테스트하기 쉬운 구조를 만드는 데 목적이 있다.
클린아키텍처
클린 아키텍처(Clean Architecture)는 소프트웨어 설계에서 의존성 방향을 명확히 하여 유연하고 테스트 가능하며 유지보수가 쉬운 시스템을 만들기 위한 아키텍처 스타일이다.
헥사고날 아키텍처와 비교
둘 다 의존성 방향은 같고, 도메인 보호, 테스트 용이성을 우선시 한다는 공통점이 있다.
그리고 아래와 같은 차이점이 있다.
| 항목 | 클린 아키텍처 | 헥사고날 아키텍처 (Ports & Adapters) |
|---|---|---|
| 주 목적 | 계층적 의존성 통제 | 외부 의존성과의 유연한 연결 |
| 중심 요소 | Entity & Use Case | Domain & Port |
| 의존성 방향 | 바깥 → 안쪽 | 바깥 → 안쪽 |
| 외부 인터페이스 명칭 | Interface Adapter | Adapter |
| 내부 인터페이스 명칭 | Use Case & Entity | Port |
| 특징 | 계층적, 유스케이스 중심 | IO와 독립된 도메인 강조 |
| 대표 적용 예시 | 복잡한 비즈니스 로직 중심 앱 | 시스템 간 통신 많은 시스템 |
결론적으로 클린는 복잡한 유스케이스 강조 앱, 헥사고날은 다양한 외부와의 연동 많은 시스템(API, 메시징 등)에 사용된다.
구조
아래 아키텍처는 다음과 같은 디렉토리 구조로 설계하는 것이 일반적이다.
com.example.hexagonal
├── domain
│ ├── model
│ │ └── Member.java
│ ├── port
│ │ ├── in
│ │ │ └── RegisterMemberUseCase.java
│ │ └── out
│ │ └── SaveMemberPort.java
│ └── service
│ └── MemberDomainService.java
├── application
│ └── service
│ └── RegisterMemberService.java
├── adapter
│ ├── in
│ │ └── web
│ │ └── MemberController.java
│ └── out
│ └── persistence
│ ├── MemberJpaEntity.java
│ ├── MemberRepository.java
│ └── MemberPersistenceAdapter.java
└── config
└── BeanConfig.java
도메인 디렉토리를 보면 model 객체가 있고, port의 하위로 in/out이 존재한다. 여기에는 interface로 구성된 클래스들이 존재하는데, 이는 외부 로직으로부터 격리되기 위해서 포트를 통해 접근하도록 설계된 것이다.
domain.service의 MemberDomainService는 model 객체가 혼자 처리할 수 없는 비즈니스 로직이나 공용 로직을 처리한다. RegisterMemberService는 UseCase에 대한 구현체이다.
adapter는 외부 소통을 담당한다. in 패키지에는 외부로부터 호출을 받아 처리하는 Controller가 있고, out에는 비즈니스 로직을 처리할때 필요한 데이터 등을 요청하기 위한 JpaEnitity이 있다. 이외에도 WebClient 등이 사용될것이다.
첫 인상
해당 구조는 도메인 비즈니스 로직을 외부와 잘 격리해 놓은 듯 하다. 하지만 기능 개발시 격리를 위해 작성해야 할 오버헤드 또한 존재한다.
Client → Client Adapter → DTO → Mapping → DTO → Client Port → Service → Service Port → Service Adapter → Domain (Model) → Mapping → DTO → Controller
데이터를 각 레이어에 전달할때도 오버헤드가 존재한다. 예를들어 adapter.MemberJpaEntity, domain.model.Member가 있다. 실무 환경에서는 외부로의 데이터 통신을 위해 MemberDto 또한 존재할것이다. 이 말은 데이터의 조회, 저장, 수정시에 MemberDto -> Member -> MemberJpaEntity의 변환을 거쳐야한다는소리이다. 또한 MemberJpaEntity에서 Member객체로 변환시 지연 로딩의 기능을 사용하지 못하게 된다.
장점
이러한 과정을 거치면서 까지 데이터를 격리하는데는 다음과 같은 장점이 있다.
- 변경 격리(Isolation)
- 순수 도메인 로직을 유지하면서, 인프라 최적화나 레거시 스키마 대응을 독립적으로 관리할 수 있다. 예를들어 DB에 존재하는 shardKey, create_at, update_at soft_deleted 추가 삭제 등이 비즈니스 로직에 영향을 끼치지 않게 할 수 있다.
- 테스트 단순화
- DB 변경이 Model 테스트를 깨뜨리지 않고, Entity 매핑 코드만 단위 테스트 대상으로 분리 가능하다. 테스트 진행시 DB 연결과 CRUD에 대한 번거로움이 있는데 이를 제외할 수 있다는건 큰 장점으로 다가온다.
- 지연로딩 제거
- JPA사용시 즉시로딩 지연로딩으로 인해 원하지 않는 쿼리를 발생시킬 수 있는데 이를 신경쓰지 않고 비즈니스 로직에 집중할 수 있다.
단점
- 지연로딩 제거
- 지연 로딩은 필요한 시점에만 데이터를 로딩하므로, 메모리 사용량을 줄일 수 있는 장점이 있다. 이를 사용할 수 없게된다.
- 변환 과정의 오버헤드
MemberDto -> Member -> MemberJpaEntity와 같이 객체를 변환하는 과정이 복잡해진다. 이를 위한 MapStruct매핑 라이브러리가 있긴하다.
- 많은 절차와 그에 따른 관리해야할 코드들
- 위에서 든 예시와 같이
Client → Client Adapter → DTO → Mapping → DTO → Client Port → Service → Service Port → Service Adapter → Domain (Model) → Mapping → DTO → Controller절차로 로직이 수행된다. 이는 코드를 관리하는 개발자가 만들어야할 파일과 코드가 늘어난다는소리이다. 이에 따라 신규 개발에서 새롭게 개발되는 api를 위해 많은 파일들을 작성해야 한다는 것 이고, 이에따라 개발 속도는 감소될 수 있다.
- 위에서 든 예시와 같이
생각 정리
헥사고날 아키텍처는 비즈니스 로직을 외부 환경으로부터 분리하여 유연하고 테스트하기 쉬운 구조를 만드는 데 목적이 있다. 내가 느낀점은 외부와의 소통을 강조한 디렉토리 구조를 제외하곤 클린 아키텍처와 매우 비슷하다고 느꼈다. 또한 열심히 개발할 당시에는 도메인 중심의 개발을 하려고 했었는데, JpaEntity와 model객체의 분리는 생각을 못 했었다. 그 이유는 새롭게 추가되는 기능이 많다보니 하나의 단계가 추가되는것이 부담으로 다가왔기 때문이다. 하지만 클린 아키텍처가 지향하는 것에대해 깊이 공감하기 때문에 이러한 구조를 따라 개발하도록 노력해야 하겠다.
'Web' 카테고리의 다른 글
| ElasticSearch를 이용한 검색 기능 만들어보기 - 0 (0) | 2025.06.18 |
|---|---|
| [book] 스프링으로 시작하는 리액티브 프로그래밍 - 0 (2) | 2025.06.11 |
| 유튜브 설계 (가상 면접 사례로 배우는 대규모 시스템 설계) (1) | 2025.06.04 |
| 검색어 자동완성 시스템 설계 (가상 면접 사례로 배우는 대규모 시스템 설계) (0) | 2025.06.02 |
| 채팅 시스템 설계 (가상 면접 사례로 배우는 대규모 시스템 설계) (5) | 2025.05.31 |