Day 5. 헥사고날 아키텍처 기초
1. 오늘의 학습 목표
- 헥사고날 아키텍처가 외부 기술과 핵심 로직의 결합 문제를 어떻게 다루는지 설명할 수 있다.
- Domain, Application Service, Port, Adapter의 역할을 구분할 수 있다.
- Inbound와 Outbound 흐름을 주문 예시에 적용할 수 있다.
- Controller, JPA, Kafka의 위치와 의존성 방향을 설명할 수 있다.
- 일반적인 계층형 아키텍처와의 차이를 구조가 아닌 의존성 관점에서 설명할 수 있다.
2. 학습 전 생각해보기
질문 1
주문 생성 규칙이 JPA Entity, Controller, Kafka 발행 코드에 흩어지면 테스트와 기술 변경이 왜 어려울까?
내 답변
질문 2
Application Service가 JpaRepository와 KafkaTemplate을 직접 알지 않게 하려면 그 사이에 무엇이 필요할까?
내 답변
질문 3
REST 요청과 Kafka 이벤트가 모두 같은 주문 처리 기능을 시작할 수 있다면 핵심 로직을 어디에 두어야 할까?
내 답변
3. 핵심 개념 설명
3.1 헥사고날 아키텍처가 해결하려는 문제
Spring 애플리케이션에서는 비즈니스 규칙이 Controller, JPA Entity, Repository 구현, Kafka Listener에 섞이기 쉽다. 그러면 웹 프레임워크나 DB 접근 방식이 바뀔 때 업무 코드까지 함께 흔들린다.
헥사고날 아키텍처는 핵심 비즈니스 로직을 안쪽에 두고, REST·DB·Kafka 같은 외부 기술을 바깥쪽에 둔다. 안쪽은 바깥쪽의 구체 기술을 직접 의존하지 않고, 필요한 상호작용을 Port라는 경계로 표현한다.
육각형 모양 자체나 폴더 이름이 핵심은 아니다. 업무 규칙이 외부 기술 없이도 이해되고 테스트되며, 의존성이 바깥에서 안쪽을 향하도록 만드는 것이 핵심이다.
3.2 Domain과 Application Service
Domain은 주문, 재고, 결제 같은 업무 개념과 규칙을 표현한다. 예를 들어 재고가 음수가 될 수 없다는 규칙이나 주문 상태가 허용된 순서로만 변한다는 규칙이 Domain에 속한다.
Application Service는 하나의 사용 사례를 진행한다. 주문 생성이라면 입력을 받고, Domain에 규칙 실행을 요청하고, 저장이나 이벤트 발행 같은 외부 작업을 Port를 통해 조정한다.
Domain은 “업무 규칙이 무엇인가”에 집중하고 Application Service는 “한 사용 사례에서 어떤 순서로 협력하는가”에 집중한다. Spring의 @Service라는 애너테이션 자체가 이 구분을 만들어 주지는 않는다.
3.3 Port와 Adapter
Port는 애플리케이션 경계에서 가능한 동작을 정의한 인터페이스다. Input Port는 외부가 애플리케이션에 요청할 수 있는 사용 사례를 나타내고, Output Port는 애플리케이션이 외부에 요구하는 기능을 나타낸다.
Adapter는 Port를 실제 기술과 연결한다. REST Controller는 HTTP 요청을 Input Port 호출로 바꾸는 Inbound Adapter다. JPA Repository 구현과 Kafka Publisher는 Output Port를 구현해 DB나 Broker와 연결하는 Outbound Adapter다.
Port는 모든 클래스 사이에 추가하는 계층이 아니다. 핵심과 외부 기술 사이의 의미 있는 경계에 둔다. 한 번만 쓰는 단순 내부 함수까지 Port로 만들면 구조만 복잡해진다.
3.4 Inbound와 Outbound
Inbound는 외부에서 애플리케이션 안으로 사용 사례를 시작하는 방향이다. REST Controller, Kafka Consumer, 배치 작업 등이 입력을 해석하고 Input Port를 호출할 수 있다.
Outbound는 애플리케이션이 실행 중 외부 자원을 사용하는 방향이다. Application Service는 “주문 저장”, “이벤트 발행” 같은 Output Port를 호출하고, JPA나 Kafka Adapter가 이를 수행한다.
Inbound와 Outbound는 데이터가 움직이는 방향만이 아니라 역할을 구분하는 말이다. Kafka Consumer는 들어오는 이벤트를 받으므로 Inbound, Kafka Producer Adapter는 이벤트를 밖으로 보내므로 Outbound다.
3.5 의존성 방향과 기술의 위치
핵심 코드는 KafkaTemplate이나 Spring Data 구현을 직접 의존하지 않는다. 오히려 외부 Adapter가 안쪽에 정의된 Port를 구현하거나 호출한다. 컴파일 의존성은 외부 기술에서 핵심 방향을 향한다.
Controller는 Inbound Adapter, JPA는 DB용 Outbound Adapter, Kafka Listener는 이벤트용 Inbound Adapter, Kafka Publisher는 이벤트용 Outbound Adapter에 놓인다. 같은 Kafka라도 들어오는지 나가는지에 따라 역할이 다르다.
이 구조 덕분에 핵심 사용 사례를 테스트할 때 DB나 Kafka 대신 Port의 테스트 대역을 사용할 수 있다. 목표는 테스트 편의만이 아니라 비즈니스 규칙이 기술 세부사항에 끌려가지 않게 하는 것이다.
3.6 계층형 아키텍처와 무엇이 다른가
일반적인 계층형 구조는 Controller → Service → Repository처럼 기술 역할별 층을 아래로 호출한다. 잘 설계하면 이것도 충분히 명확할 수 있다. 헥사고날과 폴더명만 비교해 우열을 정할 수는 없다.
차이는 주로 의존성의 기준에 있다. 헥사고날에서는 DB가 가장 아래의 필수 기반이 아니라 교체 가능한 바깥 Adapter다. Application이 정의한 Output Port를 JPA Adapter가 구현하므로 핵심이 JPA 구현을 향해 의존하지 않는다.
작은 CRUD에 Port와 Adapter를 과도하게 추가하면 얻는 것보다 코드량이 커질 수 있다. 외부 기술로부터 보호할 핵심 규칙과 변경 가능성이 있는 경계에 집중해야 한다.
4. 흐름으로 이해하기
외부 요청
↓
[Inbound Adapter: REST Controller / Kafka Consumer]
↓
[Input Port]
↓
[Application Service]
↓
[Domain]
[Application Service]
↓
[Output Port: 저장 / 이벤트 발행]
↓
[Outbound Adapter: JPA / Kafka Publisher]
↓
DB 또는 Kafka
Inbound Adapter는 외부 입력을 애플리케이션의 언어로 바꿔 Input Port를 호출한다. Application Service는 Domain 규칙을 사용하고, 외부 작업이 필요하면 Output Port에 의존한다. 구체적인 JPA와 Kafka 코드는 바깥 Adapter에 머문다.
5. 직접 채우는 핵심 질문
질문 1. 헥사고날 아키텍처가 보호하려는 “안쪽”은 무엇인가?
내 답변
답변에 포함해야 할 키워드
- 비즈니스 규칙
- 사용 사례
- 외부 기술
질문 2. Domain과 Application Service의 책임을 주문 생성 예시로 구분하라.
내 답변
답변에 포함해야 할 키워드
- 업무 규칙
- 흐름 조정
- 사용 사례
질문 3. Input Port와 Output Port를 호출 방향과 목적 관점에서 비교하라.
내 답변
답변에 포함해야 할 키워드
- 사용 사례 진입
- 외부 기능 요청
- 인터페이스
질문 4. REST Controller가 Inbound Adapter인 이유를 설명하라.
내 답변
답변에 포함해야 할 키워드
- HTTP
- 입력 변환
- Input Port
질문 5. Kafka Listener와 Kafka Publisher는 각각 어느 Adapter인가?
내 답변
답변에 포함해야 할 키워드
- Inbound
- Outbound
- 이벤트 방향
질문 6. Application Service가 KafkaTemplate을 직접 호출하면 어떤 의존성 문제가 생기는가?
내 답변
답변에 포함해야 할 키워드
- 구체 기술
- 핵심 코드
- Output Port
질문 7. JPA Adapter가 Application에 정의된 저장 Port를 구현할 때 의존성 방향을 설명하라.
내 답변
답변에 포함해야 할 키워드
- 바깥에서 안쪽
- 인터페이스 구현
- 역전
질문 8. 단순 CRUD의 모든 메서드에 Port를 추가하는 설계가 과할 수 있는 이유는 무엇인가?
내 답변
답변에 포함해야 할 키워드
- 복잡성
- 의미 있는 경계
- 비용
질문 9. 계층형과 헥사고날의 차이를 폴더명이 아닌 의존성 관점에서 설명하라.
내 답변
답변에 포함해야 할 키워드
- Repository 구현
- Port
- 외부 Adapter
6. 상황 판단 문제
상황 1
OrderService가 JpaOrderRepository, KafkaTemplate, HTTP 요청 DTO를 모두 직접 사용한다.
- 어떤 외부 기술이 핵심 사용 사례에 섞였는가?
- Input/Output Port와 Adapter로 어떤 경계를 만들 수 있는가?
내 답변
상황 2
REST Controller와 Kafka Consumer가 주문 취소 규칙을 각각 복사해 구현했다.
- 규칙 중복이 생긴 원인은 무엇인가?
- 두 Inbound Adapter가 공통으로 호출해야 할 곳은 어디인가?
내 답변
상황 3
DB를 JPA에서 다른 저장 기술로 바꾸려는데 Domain 클래스까지 대폭 수정해야 한다.
- 어떤 분리가 충분하지 않았다고 볼 수 있는가?
- 의존성은 어떤 방향이어야 하는가?
내 답변
7. 오늘의 용어 정리
| 용어 | 내가 작성하는 정의 |
|---|---|
| 헥사고날 아키텍처 | |
| Domain | |
| Application Service | |
| Port | |
| Adapter | |
| Input Port | |
| Output Port | |
| Inbound Adapter | |
| Outbound Adapter | |
| 의존성 방향 |
8. 프로젝트에 한 줄 연결
- 각 서비스의
domain은 주문·상품·재고·결제의 핵심 규칙을 담는 영역이다. - 향후
application은 사용 사례를 조정하고 Port를 통해 저장·이벤트 발행을 요청한다. - REST Controller와 Kafka Consumer는 Inbound, JPA와 Kafka Publisher는 Outbound Adapter에 해당한다.
9. 오늘의 자기 점검
- 핵심 개념을 내 말로 설명할 수 있다.
- 오늘 등장한 주요 용어를 구분할 수 있다.
- 주문 시스템에 개념을 적용할 수 있다.
- 이해하지 못한 부분을 표시했다.
- 다음 주제와 현재 주제를 혼동하지 않는다.
10. 이해하지 못한 부분
11. 3문장 요약
12. 복습 퀴즈
- 외부에서 사용 사례를 시작하는 Port는 무엇인가?
- Application이 DB 저장을 요구할 때 호출하는 경계는 무엇인가?
- Kafka Consumer는 Inbound와 Outbound 중 어디에 해당하는가?
- 구체적인 JPA 구현은 핵심과 Adapter 중 어디에 위치하는가?
- 헥사고날 아키텍처의 핵심이 육각형 폴더 구조가 아닌 이유는 무엇인가?
복습 퀴즈 정답과 해설 보기
- Input Port다.
- Output Port다. 저장 Adapter가 이를 구현한다.
- Inbound Adapter다. 외부 이벤트로 애플리케이션의 사용 사례를 시작한다.
- Outbound Adapter에 위치한다.
- 핵심은 모양이 아니라 비즈니스 로직을 외부 기술에서 분리하고 의존성을 안쪽으로 향하게 하는 것이기 때문이다.