1️⃣ 계층형 아키텍처의 한계
Controller -> Service -> Repository -> DB
이 구조를 계층형(Layered) 아키텍처 라고 한다. 이 구조의 단점은 다음과 같다.
- 변경 전파 : DB나 외부 API가 바뀌면 Service 코드까지 수정해야 한다.
- 결합도 증가 : 비즈니스 로직이 JPA, Mybatis 같은 기술 세부사항에 묶인다.
- 테스트 어려움 : DB 연결 없이 Service 로직을 테스트하기 어렵다.
이 구조의 의존성 방향은 "바깥 👉 안쪽" 이다. 즉, Controller가 Service를 사용하고 Service가 Repository를 사용한다.
👉 👉 도메인 로직이 외부 기술에 의존하는 구조
2️⃣ 헥사고날 아키텍처
DB나 API, 웹 UI 같은 기술이 도메인 로직을 몰라야 한다
[ Controller / Web / MQ ] → (Input Adapter)
↓
[ Input Port ] ← 인터페이스
↓
[ Application / UseCase(Service) ] ← 핵심 비즈니스 로직
↓
[ Output Port ] ← 인터페이스
↓
[ DB / API / File / etc. ] ← (Output Adapter)
- Service (UseCase) : 도메인 로직을 실행하는 곳
- Port : Service와 외부를 연결하기 위한 통로 (인터페이스)
- Adapter : Port의 구현체 (ex. DB 접근)
- Domain : 비즈니스의 규칙을 담는 모델
👀 Service는 Adapter를 모르고 Adapter가 Service도 모른다 >> 의존성 역전
3️⃣ 의존성 방향이 반대 ?
- 계층형 구조
- Service --> Repository (Service가 Repository를 직접 호출) 👉 Repository에 직접 의존
- 헥사고날 구조
- Service --> OutputPort <-- Adapter
- Service는 OutputPort 인터페이스만 알고 있고 실제 DB 접근 로직은 Adapter가 구현
- InputPort : 외부 --> 도메인(외부 요청을 받는 인터페이스)
- OutputPort : 도메인 --> 외부 (외부 시스템(DB, API)에 접근하는 인터페이스)
4️⃣ 스프링에서의 헥사고날
IoC (제어의 역전) 과 DI (의존성 주입) 으로 헥사고날 구현이 쉬어진다.
public interface OrderRepositoryPort {
void save(Order order);
}
@Repository
public class OrderRepositoryAdapter implements OrderRepositoryPort {
private final OrderRepository orderRepository;
@Override
public void save(Order order) {
orderRepository.save(order);
}
}
@Service
public class OrderService implements OrderUseCase {
private final OrderRepositoryPort orderRepositoryPort;
// 생성자 주입
public OrderService(OrderRepositoryPort orderRepositoryPort) {
this.orderRepositoryPort = orderRepositoryPort;
}
}
스프링이 OrderRepositoryPort 타입을 찾아 OrderRepositoryAdapter를 자동 주입해줘 헥사고날의 구조적 원리를 살릴 수 있다.
- @Service, @Repository, @Component 등으로 등록된 빈을 스캔함
- OrderSerivce가 생성될 때 생성자 파라미터인 OrderRepositoryPort 타입을 보고 이 타입을 구현한 빈 (OrderREpositoryAdapter)를 찾아 자동으로 그 객체를 주입함
5️⃣ 폴더 구조 예시
gpt의 도움 .. 👀
📦 com.example.order
┣ 📂 domain
┃ ┣ 📜 Order.java
┃ ┣ 📜 OrderItem.java
┃ ┣ 📜 Money.java
┃ ┗ 📜 DomainService.java
┣ 📂 application
┃ ┣ 📂 port
┃ ┃ ┣ 📂 in
┃ ┃ ┃ ┗ 📜 PlaceOrderUseCase.java
┃ ┃ ┗ 📂 out
┃ ┃ ┗ 📜 OrderRepositoryPort.java
┃ ┗ 📂 service
┃ ┗ 📜 PlaceOrderService.java
┣ 📂 adapter
┃ ┣ 📂 in
┃ ┃ ┗ 📂 web
┃ ┃ ┣ 📜 OrderController.java
┃ ┃ ┗ 📂 dto
┃ ┃ ┣ 📜 PlaceOrderRequest.java
┃ ┃ ┗ 📜 OrderResponse.java
┃ ┗ 📂 out
┃ ┗ 📂 persistence
┃ ┗ 📜 JpaOrderRepositoryAdapter.java
┗ 📂 config
┗ 📜 BeanConfig.java