
헥사고날 아키텍처와 클린 아키텍처 차이는 구조 이름의 싸움처럼 보이기 쉽습니다. 하지만 실무에서 더 중요한 것은 핵심 규칙을 어디에 두고, 바깥 기술과 어떤 경계를 만들고, 의존 방향을 어느 쪽으로 고정할 것인가입니다. 이번 글에서는 둘을 우열 비교가 아니라 경계, 의존 방향, ports and adapters, 과설계 판단 기준까지 포함해 실무적으로 비교합니다.
먼저 결론부터 정리하면
헥사고날 아키텍처와 클린 아키텍처는 같은 문제를 푸는 가까운 친척입니다. 둘 다 핵심 규칙을 중심에 두고, 웹 프레임워크, 데이터베이스, 메시지 브로커, 외부 API 같은 바깥 요소를 바깥으로 밀어내려 합니다. 이름보다 더 중요한 기준은 안쪽 핵심 규칙이 바깥 기술 세부사항을 몰라야 한다는 점과, 의존성이 바깥 구현에서 안쪽 정책 쪽으로 향해야 한다는 점입니다.
즉 둘의 핵심은 도식 모양보다 경계와 의존 방향입니다.
둘이 공통으로 해결하려는 문제
복잡한 시스템에서는 컨트롤러나 서비스가 요청 처리, 비즈니스 규칙, DB 저장, 외부 API 호출을 한꺼번에 다루기 쉽습니다. 이 상태의 진짜 문제는 파일 개수보다 변경이 퍼지는 방식입니다. 핵심 규칙의 변경과 기술 세부사항의 변경이 서로 묶이면, 작은 수정도 넓게 번지기 시작합니다. 헥사고날 아키텍처와 클린 아키텍처는 둘 다 이 묶임을 끊으려는 시도입니다.
헥사고날 아키텍처가 특히 강조하는 것
헥사고날 아키텍처를 이해할 때 가장 먼저 보이는 단어는 ports and adapters입니다. 포트는 안쪽이 바깥에 요구하는 역할 계약에 가깝고, 어댑터는 그 계약을 실제 기술과 연결하는 번역기입니다. 이 관점에서는 웹 요청도 입력 어댑터이고, DB도 출력 어댑터이며, 메시지 큐, CLI, 배치도 모두 애플리케이션 바깥에 놓입니다.
헥사고날 아키텍처가 주는 좋은 감각은 위와 아래의 그림보다 안쪽과 바깥쪽의 관계를 먼저 보게 만든다는 점입니다. 여러 진입점과 외부 시스템을 대칭적으로 다루는 시스템에서 특히 직관적입니다.
클린 아키텍처가 특히 강조하는 것
클린 아키텍처도 큰 방향은 비슷하지만 표현 방식이 조금 다릅니다. 클린 아키텍처는 정책과 세부 구현의 관계를 더 강하게 말합니다. 핵심 비즈니스 규칙은 안쪽에 있고, UI, 프레임워크, 데이터베이스 같은 것은 바깥쪽 세부사항으로 둡니다. 그래서 클린 아키텍처를 이해할 때는 동심원 그림 자체보다 dependency rule을 먼저 보는 편이 맞습니다.
중요한 것은 화살표가 안쪽을 향하는 구조입니다. 폴더를 몇 겹으로 나눴는지보다, 안쪽 정책이 바깥 구현 세부를 모르고도 성립하는지가 더 중요합니다.
헥사고날 아키텍처와 클린 아키텍처 차이
헥사고날은 입출력 경계를 더 또렷하게 보여준다
헥사고날 아키텍처는 포트와 어댑터라는 표현 덕분에 웹, 배치, CLI, DB, 외부 API를 모두 대칭적으로 볼 수 있게 해줍니다. 즉 컨트롤러는 위쪽, DB는 아래쪽이라는 고정 관념에서 벗어나 둘 다 애플리케이션 바깥이라는 시야를 줍니다.
클린은 계층과 정책 중심 정리가 더 강하다
클린 아키텍처는 엔터프라이즈 규칙, 애플리케이션 규칙, 인터페이스 어댑터, 프레임워크 같은 식으로 정책의 중심성과 의존 방향을 좀 더 체계적으로 설명합니다. 팀 차원에서 어디까지가 핵심 정책이고 어디부터가 세부 구현인지 합의할 때 도움이 됩니다.
실무에서는 함께 섞여 쓰이는 경우가 많다
실제로는 둘 중 하나만 순수하게 채택하는 팀보다, 클린 아키텍처의 dependency rule을 따르면서 헥사고날식 ports and adapters 언어를 함께 쓰는 팀이 많습니다. 예를 들어 유스케이스와 도메인을 안쪽에 두고, 컨트롤러와 저장소 구현을 어댑터로 보는 식입니다. 이 점 때문에 둘을 경쟁 개념이 아니라 같은 방향의 다른 설명 도구로 이해하는 편이 실무적으로 더 정확합니다.
의존 방향을 코드로 보면 더 쉽다
핵심은 안쪽 유스케이스가 바깥 기술 세부를 직접 모르면 된다는 점입니다. 아래처럼 포트를 두면 안쪽은 역할만 의존합니다.
public interface LoadOrderPort {
Order load(Long orderId);
}
public interface SaveOrderPort {
void save(Order order);
}
public interface NotifyApprovalPort {
void notifyHighValueOrder(Order order);
}public class ApproveOrderUseCase {
private final LoadOrderPort loadOrderPort;
private final SaveOrderPort saveOrderPort;
private final NotifyApprovalPort notifyApprovalPort;
public ApproveOrderUseCase(
LoadOrderPort loadOrderPort,
SaveOrderPort saveOrderPort,
NotifyApprovalPort notifyApprovalPort
) {
this.loadOrderPort = loadOrderPort;
this.saveOrderPort = saveOrderPort;
this.notifyApprovalPort = notifyApprovalPort;
}
public void execute(Long orderId) {
Order order = loadOrderPort.load(orderId);
order.approve();
saveOrderPort.save(order);
if (order.requiresAttention()) {
notifyApprovalPort.notifyHighValueOrder(order);
}
}
}이 코드의 좋은 점은 JPA, HTTP 클라이언트, Slack SDK보다 주문 승인 흐름이 먼저 보인다는 것입니다. 바로 이 감각이 헥사고날 아키텍처와 클린 아키텍처가 함께 지키려는 중심입니다.
둘은 왜 적이 아니라고 말할까
구조 이름의 우열보다 중요한 것은 핵심 규칙 보호입니다. 헥사고날은 경계 언어를 선명하게 보여주고, 클린은 의존 규칙을 계층적으로 정리해주는 쪽에 더 가깝습니다. 포트와 어댑터는 클린 아키텍처 안에서도 충분히 자연스럽고, 클린의 dependency rule은 헥사고날의 본질을 더 또렷하게 설명해줍니다.
헥사고날 아키텍처란 무엇인가: ports and adapters를 언제 쓰면 좋을까와 클린 아키텍처는 왜 어렵게 느껴질까: 계층보다 의존 방향이 핵심인 이유를 함께 보면 두 구조가 서로를 보완하는 방식이 더 잘 보입니다.
테스트 관점에서는 어떻게 보일까
둘 다 테스트 이야기가 자주 붙는 이유는 같습니다. 핵심 규칙이 바깥 기술에서 분리되면, 테스트할 때 서버와 DB를 꼭 같이 끌고 오지 않아도 되기 때문입니다. 테스트에서는 인메모리 어댑터를 붙여 핵심 규칙만 빠르게 검증할 수 있습니다.
public class InMemoryOrderAdapter implements LoadOrderPort, SaveOrderPort {
private final Map<Long, Order> store = new HashMap<>();
@Override
public Order load(Long orderId) {
return store.get(orderId);
}
@Override
public void save(Order order) {
store.put(order.getId(), order);
}
}결국 테스트성이 좋아지는 이유는 mocking 도구 때문이 아니라, 핵심 규칙이 외부 환경 없이도 성립하도록 경계를 만들었기 때문입니다.
언제 어떤 언어가 더 설명력이 좋을까
- REST API, 배치, 이벤트 소비자, CLI처럼 진입점이 여러 개라면 헥사고날의 ports and adapters 언어가 특히 직관적이다
- 정책과 구현 세부를 계층적으로 합의해야 한다면 클린 아키텍처의 language가 더 조직적이다
- 장기 유지보수와 팀 합의가 중요할수록 클린의 dependency rule 설명이 힘을 발휘한다
- 입출력 경계를 대칭적으로 보고 싶을수록 헥사고날의 안쪽과 바깥쪽 관점이 유용하다
즉 클린은 더 넓은 구조 언어로 쓰기 좋고, 헥사고날은 경계와 입출력 연결을 설명하기 좋은 언어로 쓰기 좋습니다.
언제부터 과설계가 될까
핵심 규칙보다 CRUD가 대부분일 때
단순 조회와 저장이 대부분이고 도메인 판단이 거의 없다면, 포트와 어댑터를 기능마다 촘촘하게 나누는 이득이 크지 않을 수 있습니다.
의미 없는 인터페이스가 늘어날 때
실제로 보호할 경계가 없는데도 모든 클래스마다 계약과 구현을 강제로 쪼개면 파일 수만 늘고 읽는 비용이 커집니다.
모델 복제가 목적이 되어버릴 때
요청 모델, 응답 모델, 엔티티, 도메인 모델, 결과 모델이 거의 같은 데이터를 반복 복사하는데도 경계 보호 이득이 작다면 ceremony가 커졌다고 봐야 합니다.
팀이 아직 그 구조를 운영할 준비가 안 되었을 때
작은 팀이나 초기 제품 단계에서는 완성형 구조보다 빠른 학습과 핵심 기능 검증이 더 중요할 수 있습니다. 이때는 전체 구조를 교과서처럼 다 갖추기보다, 자주 흔들리는 핵심 규칙 하나부터 분리하는 편이 현실적입니다.
실무에서는 무엇부터 지키면 될까
- 핵심 비즈니스 규칙이 어디에 있는지 찾는다
- 그 규칙이 프레임워크와 저장 기술에 직접 묶여 있는지 본다
- 변경 가능성이 큰 외부 연동만 먼저 경계 뒤로 민다
- 테스트에서 핵심 규칙을 독립적으로 검증할 수 있는지 본다
- 구조 비용보다 경계 보호 가치가 커질 때 추상화를 늘린다
이 순서의 장점은 구조가 문제를 따라 자란다는 점입니다. 이름을 먼저 고르는 것보다 무엇을 보호할 것인지 먼저 정하게 됩니다.
같이 읽으면 좋은 글
헥사고날 아키텍처란 무엇인가: ports and adapters를 언제 쓰면 좋을까, 클린 아키텍처는 왜 어렵게 느껴질까: 계층보다 의존 방향이 핵심인 이유, 레이어드 아키텍처란 무엇인가: 계층을 나누는 이유, 책임, 흔한 오해, 인터페이스는 왜 필요할까: 구현 분리보다 더 중요한 설계상의 이점 정리를 이어서 읽어보면 이해가 더 단단해집니다.
정리
헥사고날 아키텍처와 클린 아키텍처 차이는 같은 문제를 다른 각도에서 설명하는 데 가깝습니다. 헥사고날은 ports and adapters를 통해 경계를 더 선명하게 보여주고, 클린은 정책 중심의 계층과 dependency rule을 더 강하게 정리합니다. 하지만 둘 모두 핵심 규칙을 안쪽에 두고, 바깥 기술의 변화가 그 규칙을 함부로 흔들지 못하게 만드는 것을 목표로 합니다.
그래서 실제로 먼저 물어야 할 질문은 내 시스템에서 정말 보호해야 할 핵심 규칙은 무엇인지, 그리고 그 규칙이 지금 바깥 기술 선택 때문에 흔들리고 있는지입니다. 외부 참고로는 Uncle Bob의 Clean Architecture 글, Martin Fowler의 Presentation Domain Data Layering, Microsoft Learn의 N-tier Architecture, Hexagonal architecture 개요를 함께 보면 맥락을 더 넓게 잡는 데 도움이 됩니다.