Skip to Content

Step 08. 세 형제, 하나의 원리 — 헥사고날 · 어니언 · 클린

만화로 보는 요약 — 먼저 읽어보세요

만화로 보는 요약 — 먼저 읽어보세요

헥사고날·어니언·클린 아키텍처는 표현은 달라도, 핵심을 안쪽에 두고 바깥 세부사항을 포트와 어댑터로 교체 가능하게 만든다.

면접 실전 질문: ① 포트와 어댑터는 각각 무엇인가? ② 의존성은 어느 방향을 향해야 하는가? ③ 세 스타일의 공통 원리는 무엇인가?


이 장의 질문: 2005년, 2008년, 2012년에 각각 다른 이름으로 발표된 세 아키텍처는 왜 사실상 같은 것인가? 그리고 “셋 중 무엇을 골라야 하나요”가 왜 잘못된 질문인가?


1. 비대칭이라는 병 — Cockburn의 관찰

앞 장의 끝에서 남긴 긴장을 상기하자: DDD는 “도메인이 심장”이라 선언했지만, 레이어드의 기하학에서 도메인은 여전히 인프라 위에 앉아 아래를 내려다본다. 이 긴장을 끝낸 사람은 DDD 진영이 아니라, 애자일 방법론자 Alistair Cockburn이었다.

Cockburn의 출발점은 레이어드 그림 자체에 대한 의심이다. 그 그림에서 UI는 에, DB는 아래에 있다. 왜? 둘의 차이가 뭔가? UI는 애플리케이션을 구동하는 바깥 세계이고, DB는 애플리케이션이 구동하는 바깥 세계다. 방향만 다를 뿐 둘 다 바깥이다. 위/아래라는 배치는 본질이 아니라 관습 — 그리고 그 관습이 병을 만든다. “위쪽 바깥”(UI)과의 결합은 비즈니스 로직에 화면 논리가 스미게 하고, “아래쪽 바깥”(DB)과의 결합은 Step 06에서 본 모든 병을 만든다.

2005년, Cockburn은 이 비대칭을 지운 그림을 발표한다 — 헥사고날 아키텍처(Hexagonal Architecture), 그가 더 선호한 이름으로는 포트와 어댑터(Ports and Adapters). 선언된 의도는 이렇다:

“애플리케이션이 사용자, 프로그램, 자동화 테스트, 배치 스크립트에 의해 동등하게 구동될 수 있게 하라. 그리고 최종 실행 환경의 장치와 데이터베이스로부터 격리된 채 개발·테스트될 수 있게 하라.”

문장을 뜯어보면 두 가지가 선언돼 있다. 첫째, 대칭성 — 사용자와 테스트 스크립트가 “동등하게” 구동한다는 것은, UI가 특권적 위치(맨 위층)를 잃는다는 뜻이다. 둘째, 그 대칭성의 결실로서의 테스트가능성 — 흔히 헥사고날의 동기를 “테스트 때문”이라고 요약하지만, 정확히는 비대칭을 제거하니 테스트 하네스가 UI와 같은 자격을 얻게 된 것이다. 원인은 대칭성, 결실이 테스트가능성이다.

그림은 이렇게 바뀐다. 위아래가 없는 육각형 하나. 안쪽에 애플리케이션(도메인 + 유스케이스), 바깥에 세상 전부(UI, DB, 메시지 큐, 외부 API, 테스트). 그리고 모든 화살표는 안을 향한다. (참고로 육각형의 “6”에는 아무 의미가 없다 — 포트 여러 개를 그릴 자리가 필요했을 뿐이라고 Cockburn 본인이 밝혔다.)

2. 포트와 어댑터 — 육각형의 해부

이름의 두 요소가 구조의 전부다.

**포트(port)**는 육각형 변에 뚫린 구멍 — 정확히는 애플리케이션이 소유한, 의도의 이름이 붙은 계약이다. “주문을 넣기 위한 포트”, “주문 데이터를 얻기 위한 포트”처럼 포트의 이름은 기술이 아니라 목적을 말한다. 코드로는 인터페이스다. Step 03에서 예습한 문장이 마침내 제 이름을 얻는 순간이다: 계약은 사용자(애플리케이션)의 것주문저장소 인터페이스는 도메인 패키지에 살고, 메서드는 정책의 언어를 쓴다.

포트에는 두 방향이 있다.

  • 주도 포트(driving/primary port): 바깥이 애플리케이션을 부르는 쪽. “주문을 접수한다” 같은 유스케이스의 입구. 이 포트를 두드리는 것은 REST 컨트롤러일 수도, CLI일 수도, 테스트 코드일 수도 있다 — 셋 다 동등한 자격이다.
  • 피주도 포트(driven/secondary port): 애플리케이션이 바깥을 부르는 쪽. “주문을 저장한다”, “알림을 보낸다”. 이 포트 뒤에 서는 것은 MySQL일 수도, 인메모리 목(mock)일 수도 있다 — 역시 동등한 자격이다.

**어댑터(adapter)**는 포트의 언어와 특정 기술의 언어 사이의 번역기다. REST 컨트롤러는 HTTP를 유스케이스 호출로 번역하는 주도 어댑터, JPA 리포지토리 구현체는 주문저장소 계약을 SQL로 번역하는 피주도 어댑터다. 여기서 화살표를 확인하라 — 주도 어댑터는 포트를 호출하므로 안을 향하고, 피주도 어댑터는 포트를 구현하므로 역시 안을 향한다(DIP). 양쪽 바깥이 모두 안을 향하는 이 대칭이, Step 03에서 예고했던 그 그림이다.

테스트가능성이 왜 “결실”인지 이제 분명하다. 주도 포트에 테스트를 꽂고 피주도 포트에 목을 꽂으면, DB도 서버도 없이 애플리케이션 전체가 돌아간다. 테스트를 위한 특별 장치가 아니라, 구조가 원래 그렇게 생긴 것이다.

3. 동심원의 재진술 — 어니언, 그리고 클린

같은 원리가 두 번 더, 다른 그림으로 발표된다.

어니언 아키텍처(Palermo, 2008). Jeffrey Palermo는 육각형 대신 동심원을 그렸다. 중심에 도메인 모델, 그 둘레에 도메인 서비스, 애플리케이션 서비스, 그리고 가장 바깥 껍질에 인프라·UI·테스트. 규칙은 두 개다: 모든 결합은 중심을 향한다, 그리고 안쪽 원이 인터페이스를 정의하고 바깥 원이 구현한다. Palermo가 특히 힘줘 말한 것은 이것이다 — “데이터베이스는 중심이 아니다. 바깥이다.” Step 06의 병에 대한 직격이다.

클린 아키텍처(Martin, 블로그 2012 / 단행본 2017). Robert Martin은 자신의 글에서 헥사고날, 어니언, 그리고 그 유사 구조들을 나란히 놓고 “이들은 모두 같은 목표를 향한다”고 선언한 뒤, 그것들을 하나의 그림으로 통합하는 것을 목표로 내걸었다 — 즉 클린 아키텍처는 발명이라기보다 명시적인 종합이다. 동심원 네 겹(엔티티 → 유스케이스 → 인터페이스 어댑터 → 프레임워크와 드라이버)과, 이 책 전체에서 가장 많이 인용될 한 문장:

의존성 규칙(The Dependency Rule): 소스 코드 의존성은 오직 안쪽을 향해야만 한다.

클린이 종합에 더한 고유 기여를 꼽으라면 둘이다. 첫째, 유스케이스 층의 명시 — “도메인 규칙”(엔티티: 어느 애플리케이션에서든 참인 규칙)과 “애플리케이션 규칙”(유스케이스: 이 시스템의 시나리오)을 구분해 두 겹으로 나눴다. 앞 장의 어휘와 잇자면 클린의 유스케이스 층은 DDD의 애플리케이션 층, 엔티티는 도메인 층에 해당한다 — Evans의 4층이 동심원으로 말려 들어간 셈이다. 둘째, 경계 통과의 역학을 못박은 것 — 제어 흐름이 화살표를 거슬러야 할 때 다형성으로 건넌다는, Step 03에서 본 바로 그 기제의 명문화다.

4. 세 형제 비교표 — 그리고 잘못된 질문

이제 셋을 나란히 놓자.

헥사고날 (2005)어니언 (2008)클린 (2012/2017)
그림육각형, 안/밖동심원 4겹동심원 4겹
핵심 규칙바깥은 포트를 통해서만, 화살표는 안으로모든 결합은 중심으로의존성 규칙 (소스 의존성은 안으로만)
계약의 소유포트 = 애플리케이션 소유안쪽 원이 인터페이스 정의안쪽이 추상 소유
내부 구조의 규정없음 (육각형 안은 각자 알아서)도메인모델/도메인서비스/앱서비스로 규정엔티티/유스케이스로 규정
강조점대칭성, 구동자의 동등함DB는 중심이 아니다종합, 프레임워크·DB·UI는 세부사항

표에서 봐야 할 것은 차이의 크기다. 첫 세 행 — 원리에 해당하는 부분 — 은 문장만 다르고 내용이 같다. 차이는 넷째 행뿐인데, 이는 원리가 아니라 상세함의 수준이다: Cockburn은 경계(포트/어댑터)에 집중하느라 육각형 내부의 구조는 규정하지 않았고, Palermo와 Martin은 내부의 층까지 그렸다. 즉 어니언과 클린은 헥사고날과 경쟁하는 대안이 아니라, 헥사고날이 비워둔 내부를 채운 상세도에 가깝다.

그래서 “셋 중 무엇을 골라야 하나요?”는 잘못된 질문이다. 그것은 세 가지 다른 구조 중의 선택이 아니라 같은 원리를 부르는 세 가지 어휘 중의 선택이다. 팀이 포트/어댑터라는 말을 쓰든, 유스케이스 층이라는 말을 쓰든, 지켜지는 것은 하나다 — 다음 절의 그 문장.

5. 수렴의 의미 — DIP, 시스템의 크기로

이 책의 프레임을 상기하자: 우리는 진화가 아니라 수렴을 보고 있다. 7년에 걸쳐 세 사람이 서로 다른 문제의식(테스트, DB 중심성, 종합)에서 출발해 같은 지점에 도착했다. 그 지점을 Part 1의 언어로 쓰면 한 문장이다:

의존성 역전 원칙을 클래스 사이가 아니라 시스템 전체의 경계에 적용하라. 정책이 추상(포트)을 소유하고, 모든 세부사항이 그 추상을 향하게 하라.

Step 03의 두 질문에 대입하면 — 선을 어디에 긋는가: 도메인/세상 사이에. 화살표는 어느 쪽으로 건너는가: 안으로, 예외 없이. 이것이 전부다.

이 한 문장이 Step 06의 병들을 어떻게 푸는지 대차대조표를 만들어보자.

  • 스키마가 도메인을 정의한다 → 역전된다. 도메인 모델이 먼저 서고, 스키마는 피주도 어댑터 뒤의 번역 문제가 된다. 심장의 자리에 (SAP가 요구한 대로) 가장 추상적인 것이 앉는다.
  • 정책이 세부사항의 인질주문 한도 검증은 이제 포트에 목을 꽂고 밀리초에 테스트된다.
  • 가장 비싼 결정이 가장 먼저 → “DB는 세부사항”이라는 (충격 요법으로 유명한) 문장의 실제 의미가 이것이다. DB가 안 중요하다는 게 아니라 — 그 결정을 미룰 수 있는 구조가 됐다는 것. Step 02의 기준(좋은 아키텍처 = 결정을 미룰 수 있게 하는 구조)의 교과서적 달성이다.
  • 전단층의 오배치 → 절반만 풀린다. 정직하게 갈라 적자. 깊이 축은 바로잡힌다 — 가장 빨리 바뀌는 것(기술)이 가장 바깥 껍질로 가는 배치는 Brand의 건물 층 순서 그대로다. 그러나 수직 절단의 문제는 남는다 — “부분 취소 추가”라는 기능 변경은 헥사고날에서도 여전히 주도 어댑터부터 피주도 어댑터까지 모든 링을 관통한다. 한 기능의 변경을 한 모듈에 가두는 것은 링의 일이 아니라 육각형 내부를 도메인/컨텍스트로 쪼개는 별개의 결정(앞 장의 바운디드 컨텍스트)이고, 다음 장들의 주제다.

6. 청구서 — 공짜가 아니다

제1법칙(모든 것은 트레이드오프)은 세 형제에게도 적용된다. 지불하는 것들:

  • 간접의 비용. 컨트롤러가 서비스를 직접 부르면 한 단계인 것이, 주도 어댑터 → 포트 → 유스케이스 → 피주도 포트 → 어댑터의 다섯 단계가 된다. 코드 탐색(“이 인터페이스의 구현이 어디지?”)의 인지 비용도 함께 온다.
  • 매핑의 비용. 경계마다 모델이 갈린다 — 요청 DTO ↔ 도메인 객체 ↔ 영속 엔티티. 같은 값이 세 번 옮겨 적히는 보일러플레이트는 이 구조의 실제 청구서다.
  • 과잉의 위험. 지킬 불변식이 없는 CRUD에 이 구조를 세우면, Step 06의 변호가 그대로 역방향으로 적용된다 — 빈 금고에 요새를 지은 것이다. 앞 장(묘지)의 언어로, 힘과 어긋난 구조는 양이 아니라 정렬의 문제다.

계산법은 Step 04가 줬다: 이 구조가 사는 것은 변경용이성(도메인 축)과 테스트가능성이고, 파는 것은 단순성과 초기 속도다. 도메인 복잡도와 시스템 수명이 그 환율을 정한다.


7. 흔한 오해 바로잡기

오해 1: “어댑터 = DAO의 새 이름.” 방향이 다르다. DAO는 “DB가 제공하는 것”을 감싼 데이터 접근 어휘이고, 어댑터는 애플리케이션이 소유한 포트의 계약을 기술로 번역하는 쪽이다. 판별법은 인터페이스의 거주지와 어휘 — 인터페이스가 영속 패키지에 살거나 메서드가 SQL을 닮았다면(selectByStatusAndDate...), 그것은 포트가 아니라 DAO다.

오해 2: “포트마다 육각형 변이 필요하다 / 6개 제한이 있다.” 육각형은 은유다. 변의 수에 의미가 없다고 Cockburn이 직접 밝혔다.

오해 3: “헥사고날 = 모든 층에서 객체를 매핑하는 것.” 매핑은 경계의 결과이지 목적이 아니다. 목적은 화살표의 방향이다. 작은 시스템에서 도메인 객체를 그대로 직렬화해도 의존성 규칙만 지켜지면 헥사고날이고, DTO 7종을 갖춰도 도메인이 JPA를 import하면 헥사고날이 아니다.

오해 4: “프레임워크 독립 = 스프링을 쓰지 말라는 뜻.” 아니다 — 도메인이 스프링을 import하지 않게 하라는 뜻이다. 바깥 껍질에서 스프링은 훌륭한 조립 도구이자 어댑터 재료다(Step 03의 오해 1과 같은 구도: 컨테이너는 조립을 도울 뿐, 방향은 설계가 정한다).

오해 5: “세 형제는 경쟁하는 대안이다.” 4절의 결론 그대로 — 어휘가 다른 한 원리다. 팀에서 “우리 어니언으로 갈까 클린으로 갈까”라는 논쟁이 벌어진다면, 그 시간에 포트의 소유권이 어디 있는지를 확인하는 편이 낫다.


8. 요약, 그리고 다음 장으로

  • 헥사고날(2005)의 계기는 비대칭의 제거 — UI도 DB도 똑같은 바깥이다. 테스트가능성은 그 대칭성의 결실.
  • 포트 = 애플리케이션이 소유한 의도의 계약(주도/피주도), 어댑터 = 포트↔기술의 번역기. 양쪽 바깥의 화살표가 모두 안을 향한다.
  • 어니언(2008)과 클린(2012/2017)은 같은 원리의 동심원 재진술 — 어니언은 “DB는 중심이 아니다”를, 클린은 의존성 규칙과 유스케이스 층, 그리고 명시적 종합을 더했다.
  • 셋의 차이는 원리가 아니라 어휘와 상세도다. 이것은 진화가 아니라 수렴 — DIP를 시스템 경계로 확장한 하나의 답.
  • 청구서: 간접·매핑·인지 비용. 지킬 불변식이 없다면 빈 금고의 요새다.

다음 장 예고: 여기까지의 이야기는 전부 한 프로세스 안의 기하학이었다. 그런데 2010년대, 화살표가 아니라 팀이 문제라는 것이 분명해진다 — 100명이 하나의 코드베이스에 커밋할 때 생기는 고통은 어떤 내부 구조로도 풀리지 않았다. 아키텍처를 밀어내는 가장 힘센 압력, 조직. 1968년에 이미 그것을 말한 사람이 있었다 — Melvin Conway.


원전

연도문헌이 장에서의 역할
2005Cockburn, Hexagonal Architecture (Ports and Adapters) (alistair.cockburn.us)대칭성 선언, 포트/어댑터
2008Palermo, The Onion Architecture (블로그 연작)동심원 재진술, “DB는 중심이 아니다”
2012Martin, The Clean Architecture (블로그)의존성 규칙, 유사 구조들의 명시적 종합
2017Martin, Clean Architecture (단행본)종합의 확장 — 정책/세부사항, 결정의 연기
Last updated on