Web Adapter → UseCase Port → Service(Application) → Out Port → Persistence Adapter
• Controller가 Service 구현체를 전혀 모르고
• 오직 UseCase 인터페이스만 의존
• 도메인은 Spring / JPA / Web 을 전혀 모름
패키지 구조는 기술적인 계층이 아닌 아키텍처의 의도(In/Out, Port/Adapter)가 명확히 드러나도록 구성했습니다.
demo.hexagonal
├── 📂 adapter # [Infra] 외부 세계와 소통하는 어댑터
│ ├── 📂 in # Driving Adapter (요청을 받아들이는 곳)
│ │ └── 📂 web # Web Controller, Web DTO
│ └── 📂 out # Driven Adapter (요청을 내보내는 곳)
│ └── 📂 persistence # JPA Entity, Repository Impl, Mapper
│
├── 📂 application # [App] 도메인과 어댑터를 연결하는 오케스트레이션
│ ├── 📂 port # 인터페이스 (Port) 정의
│ │ ├── 📂 in # UseCase Interface (Input Port)
│ │ └── 📂 out # Repository Interface (Output Port)
│ └── 📂 service # UseCase 구현체 (트랜잭션 관리, 흐름 제어)
│
└── 📂 domain # [Core] 외부 의존성이 전혀 없는 순수 비즈니스 로직
├── 📂 exception # 도메인 비즈니스 예외 (BoardException)
└── 📂 model # 핵심 도메인 모델 (Pure Kotlin Class)
🧩 3. 각 레이어의 역할 상세 분석
헥사고날 아키텍처는 각 구역의 역할이 매우 엄격하게 나뉩니다.
🟢 Adapter Layer (바깥 고리)
애플리케이션과 외부 세계를 연결하는 관문입니다.
In Adapter (Web): HTTP 요청을 받아 자바/코틀린 객체로 파싱하고, 유효성을 검증한 뒤 UseCase를 호출합니다.
Out Adapter (Persistence): Port 인터페이스를 구현(implements)하여 실제 DB(JPA, MyBatis)에 접근합니다. 도메인 객체를 JPA 엔티티로 변환(Mapping)해서 저장하는 역할을 수행합니다.
🟠 Application Layer (중간 고리)
도메인과 어댑터를 연결하는 조정자(Orchestrator) 역할입니다.
Port (In/Out): 어댑터와 애플리케이션이 소통하는 명세(Interface)입니다.
Service: In-Port를 구현합니다. 트랜잭션(@Transactional)을 관리하고, 도메인 객체의 메서드를 호출하여 비즈니스 흐름을 만듭니다.
🔴 Domain Layer (핵심 코어)
소프트웨어의 존재 이유이자 핵심 가치입니다.
Model: 프레임워크(Spring)나 기술(DB)에 의존하지 않는 순수한 POJO/POKO 객체입니다.
Logic: "게시글 제목은 비어있을 수 없다", "수정 시 내용이 변경되어야 한다" 같은 순수 비즈니스 규칙이 이곳에 위치합니다.
[Client]
↓ 1. POST /api/boards (JSON 요청)
[BoardController] (Web Adapter)
↓ 2. CreateBoardRequest → Command 변환 및 UseCase 호출
[BoardService] (Application)
↓ 3. 순수 도메인 객체(Board) 생성
↓ 4. 저장 인터페이스(Port) 호출
[BoardPersistenceAdapter] (Persistence Adapter)
↓ 5. 도메인(Board) → 엔티티(JpaEntity) 변환 (Mapper)
↓ 6. JPA Repository save() 호출
[BoardJpaRepository] (Hibernate)
↓ 7. INSERT SQL 실행
[Database] (MySQL/H2)
↑ 8. 저장된 데이터 반환 (Auto Increment ID 포함)
[BoardPersistenceAdapter]
↓ 9. 엔티티 → 도메인 변환 후 반환
[BoardService]
↓ 10. 생성된 도메인 객체 반환
[BoardController]
↓ 11. 도메인 → BoardResponse DTO 변환
[Client]
← 12. 201 Created 응답 (JSON)
💡 흐름 요약
Request: 웹 요청(DTO)이 들어오면 Command 객체로 변환됩니다. (이 단계에서 입력값 검증 수행)
Input Port: Controller는 Service 구체 클래스 대신 UseCase 인터페이스를 호출합니다.
Domain Logic: Service는 Board 도메인 객체를 생성하거나 도메인 메서드를 호출하여 비즈니스 로직을 수행합니다.
Output Port: Service는 저장을 위해 RepositoryPort 인터페이스를 호출합니다.
Persistence: 구현체인 Adapter가 Domain을 Entity로 변환(Mapping)하여 DB에 저장합니다.
Response: 역순으로 돌아가며, 최종적으로 Response DTO로 변환되어 클라이언트에게 반환됩니다.
모든 의존성은 바깥쪽(Adapter)에서 안쪽(Domain) 으로만 향합니다.
Domain은 Application, Adapter에 대해 전혀 알지 못합니다.Application은 Adapter에 대해 알지 못합니다 (Port 인터페이스를 통해서만 소통).Mapper를 통해 변환됩니다.