소프트웨어 아키텍처 비교 - Layered vs Hexagonal vs Clean vs CQRS
토이 프로젝트를 시작하려고 하니까 제일 먼저 드는 고민이 있었다.
“아키텍처 뭐 써야 하지?”
실무에서는 Layered Architecture만 써봤다. Controller → Service → Repository 흐름. 익숙하고 편하긴 한데, 뭔가 더 좋은 구조가 있지 않을까 싶었다.
그래서 요즘 많이 언급되는 아키텍처들을 비교해봤다.
아키텍처, 왜 중요할까?
아키텍처는 “코드를 어떻게 배치할 것인가”에 대한 답이다.
잘 짜여진 아키텍처는 이런 장점이 있다.
- 기능 추가할 때 어디에 코드를 넣어야 할지 명확함
- 하나 수정했을 때 다른 데 영향이 적음
- 테스트 작성이 쉬움
- 새로운 팀원이 와도 구조 파악이 빠름
반대로 아키텍처가 엉망이면? 기능 하나 추가하는데 여기저기 수정해야 하고, 테스트는 꿈도 못 꾸고, 레거시 지옥에 빠지게 된다.
아키텍처별 특징 비교
1. Layered Architecture (계층형)
Spring Boot 프로젝트 만들면 기본으로 나오는 구조다.
Presentation(Controller) → Business(Service) → Persistence(Repository) → Database 순서로 위에서 아래로 흐르는 단순한 구조. 이해하기 쉽고 빠르게 개발할 수 있다.
근데 실무에서 이런 경험 있지 않나?
- Repository 수정했더니 Service도 수정해야 함
- Service 테스트하려면 DB 연결이 필요함
- Entity 필드 하나 추가했더니 줄줄이 수정
이게 Layered의 고질적인 문제다. 위 계층이 아래 계층에 직접 의존하기 때문에 발생하는 것.
2. Hexagonal Architecture (헥사고날 / Ports & Adapters)
도메인을 중심에 두고, 외부와는 Port(인터페이스)로 연결하는 구조다.
바깥쪽에서 Web, CLI, Message Queue 같은 입력 채널들이 Input Port를 통해 중앙의 Domain(순수 비즈니스 로직)으로 들어오고, Domain은 Output Port를 통해 JPA, Redis, 외부 API 같은 인프라에 접근한다.
핵심은 도메인이 외부 기술을 모른다는 것. DB가 MySQL이든 MongoDB든, 도메인 코드는 변경할 필요가 없다. Port(인터페이스)만 알고 있으니까.
3. Clean Architecture
Uncle Bob(로버트 마틴)이 제안한 구조다. Hexagonal과 비슷하지만 계층이 더 명확하게 나뉘어 있다.
가장 안쪽에 Entities(Domain)가 있고, 그 바깥에 Application 계층, 그 바깥에 Interface Adapters, 가장 바깥에 Frameworks & Drivers가 있다. 마치 양파처럼 겹겹이 둘러싸인 구조.
의존성은 항상 안쪽으로만 향해야 한다는 원칙이 있다. 바깥 계층은 안쪽 계층을 알지만, 안쪽 계층은 바깥 계층을 모른다.
Hexagonal보다 더 엄격해서 대규모 엔터프라이즈 프로젝트에 적합하다. 다만 학습 곡선이 높고 작은 프로젝트에는 오버엔지니어링이 될 수 있다.
4. CQRS (Command Query Responsibility Segregation)
읽기와 쓰기를 완전히 분리하는 패턴이다.
Command(쓰기) 쪽은 정규화된 Write Model을 사용해서 Master DB에 저장하고, Query(읽기) 쪽은 비정규화된 Read Model을 사용해서 Slave DB에서 조회한다. 두 DB는 동기화되어 있고.
이커머스를 생각해보면, 상품 조회는 하루 100만 건인데 주문은 1만 건일 수 있다. 이럴 때 읽기 모델을 따로 최적화해서 성능을 끌어올릴 수 있다.
다만 복잡도가 높고, 데이터 동기화 이슈가 있어서 모든 프로젝트에 적합하진 않다.
식당으로 비유하면
각 아키텍처를 식당에 비유해보면 이해가 쉽다.
Layered = 동네 분식집
손님 → 카운터 → 주방장 → 냉장고 순서로 연결되어 있다. 주방장이 냉장고 위치를 직접 알고 있다. 냉장고 옮기면? 주방장이 움직이는 방식도 바꿔야 한다.
단순하고 빠르게 장사 시작할 수 있지만, 확장하기 어렵다.
Hexagonal = 프랜차이즈 본사
배달앱, 키오스크, 전화 주문이 들어오면 “주문 규격서(Input Port)”에 맞춰서 본사 레시피(Domain)로 전달된다. 본사 레시피는 “재료 규격서(Output Port)”에 맞춰서 CJ든 오뚜기든 납품업체한테 재료를 요청한다.
본사는 레시피(Domain)만 알면 된다. 주문이 배달앱에서 오든 키오스크에서 오든, 재료가 CJ에서 오든 오뚜기에서 오든 상관없다. 규격서(Port)만 맞추면 되니까.
Clean = 대기업 R&D 센터
프랜차이즈보다 더 엄격한 규정과 절차가 있다. 모든 게 문서화되어 있고 역할 분리가 명확하다.
10년 이상 운영할 대규모 시스템에 적합하지만, 작은 가게에는 과하다.
CQRS = 대형 쇼핑몰 + 물류센터 분리
손님이 보는 매장(조회)과 물류센터(저장)를 완전히 분리한다. 매장은 보기 좋게 진열하고, 물류센터는 효율적으로 적재한다.
조회 트래픽이 압도적으로 많을 때 효과적이다.
비교 요약표
| 구분 | Layered | Hexagonal | Clean | CQRS |
|---|---|---|---|---|
| 복잡도 | 낮음 | 중간 | 높음 | 높음 |
| 테스트 용이성 | 낮음 | 높음 | 높음 | 중간 |
| 도메인 보호 | 약함 | 강함 | 매우 강함 | 상황별 |
| 학습 곡선 | 낮음 | 중간 | 높음 | 높음 |
| 적합한 규모 | 소~중형 | 중~대형 | 대형 | 읽기 많은 서비스 |
상황별 선택 가이드
Layered를 선택해야 할 때
- 빠르게 MVP 만들어서 검증하고 싶을 때
- 기능이 단순하고 복잡한 비즈니스 로직이 없을 때
- 소규모 팀에서 빠르게 개발해야 할 때
Hexagonal을 선택해야 할 때
- 도메인 로직이 복잡하고 중요할 때
- 테스트 코드를 잘 작성하고 싶을 때
- DB나 외부 API가 바뀔 가능성이 있을 때
- 중장기적으로 유지보수할 프로젝트일 때
Clean을 선택해야 할 때
- 대규모 엔터프라이즈 프로젝트일 때
- 팀이 크고 역할 분리가 필요할 때
- 10년 이상 장기 운영할 시스템일 때
- 규제가 엄격한 도메인 (금융, 의료 등)
CQRS를 선택해야 할 때
- 읽기가 쓰기보다 훨씬 많을 때
- 복잡한 조회 쿼리가 많을 때
- 읽기 성능 최적화가 중요할 때
- 이벤트 소싱과 함께 사용할 때
나의 선택: Hexagonal
토이 프로젝트(CouponFit Commerce)에는 Hexagonal을 선택했다.
이유는 이렇다.
- 이커머스라서 도메인 로직(쿠폰, 주문, 결제)이 중요함
- 테스트 코드 포트폴리오를 만들고 싶음
- Clean은 토이 프로젝트에 과함
- 당장 CQRS까지는 필요 없음
- 면접에서 “왜 이 아키텍처를 선택했나요?”에 답할 수 있어야 함
마무리
아키텍처에 정답은 없다. 상황에 맞는 선택이 있을 뿐이다.
중요한 건 “왜 이 아키텍처를 선택했는가”를 설명할 수 있는 것. 그게 실력이고, 면접에서도 좋은 인상을 줄 수 있다.
다음 글에서는 Hexagonal Architecture를 깊게 파보자. Port와 Adapter가 뭔지, 실제로 어떻게 코드를 짜는지 알아본다.
다음 글: [Hexagonal Architecture 제대로 이해하기 - Port와 Adapter란?]