콘텐츠로 바로가기

Layered, Clean & Hexagonal Architecture

관심사 분리와 의존성 역전을 통해 핵심 비즈니스 로직을 외부 환경으로부터 물리적으로 보존하는 아키텍처 설계 사상과 구현 기법을 다루는 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

system-architecture-distributed-systemssystem-architecturedistributed-systemsfoundationsarchitectural-patternslayeredcleanhexagonal-architecture9 min read

1. Overview

계층형, 클린, 헥사고날 아키텍처(Layered, Clean & Hexagonal Architecture)는 외부의 변화(UI 프레임워크 변경, DB 교체)로부터 소프트웨어의 핵심 '비즈니스 로직(Domain)'을 지켜내기 위해 코드의 의존성 방향을 통제하는 설계의 마스터클래스입니다.

학습자는 가장 직관적이지만 DB 주도 개발로 빠지기 쉬운 **계층형 아키텍처(Layered Architecture)**의 물리적 뼈대(Web \rightarrow Service \rightarrow Repository)를 해부합니다. 나아가 "고수준(도메인)이 저수준(DB)에 의존하면 안 된다"는 의존성 역전 원칙(DIP)을 활용하여, 비즈니스 룰을 보호하는 **클린 아키텍처(Clean Architecture)**의 동심원 구조를 뜯어봅니다. 마지막으로, 애플리케이션의 핵심을 육각형 정중앙에 두고, 모든 입출력을 포트(Port)와 어댑터(Adapter)로 플러그인처럼 갈아 끼우는 **헥사고날 아키텍처(Ports and Adapters)**를 통해 완벽한 테스트 가능성(Testability)과 프레임워크 독립성을 확보합니다.

2. Scope & Boundaries

In-Scope

  • Layered Architecture: Presentation (Web), Business Logic (Service), Data Access (Repository) 3계층과 하향식(Top-down) 의존성 흐름.
  • Dependency Inversion Principle (DIP): 인터페이스를 이용해 의존성 화살표의 방향을 역전시키는 핵심 기법.
  • Clean Architecture: 엔티티(Entity), 유스케이스(Use Case), 컨트롤러/프레젠터, 프레임워크/드라이버 동심원 구조.
  • Hexagonal Architecture (Ports & Adapters): 인바운드/아웃바운드 포트, 어댑터(Web Adapter, DB Adapter).

Out-of-Scope

  • 마이크로서비스 간의 통신 구조: 분산 아키텍처 맵핑 \rightarrow 07-01-01 Monolithic to Microservices Evolution으로 위임.
  • 도메인 주도 설계(DDD)의 전략적 패턴: Bounded Context 도출 \rightarrow DDD 전문 영역으로 이관 (본 문서에서는 전술적 분리만 다룸).

Boundaries

  • 비즈니스 로직 보호의 물리적 한계: 클린/헥사고날 아키텍처는 코드베이스에 수많은 인터페이스(Port)와 DTO(Data Transfer Object), 매퍼(Mapper) 클래스를 강제합니다. 도메인 엔티티와 JPA 엔티티(DB 모델)를 분리해야 하므로 데이터를 변환하는 오버헤드가 발생합니다. 이는 단순 CRUD 애플리케이션에서는 엄청난 **오버엔지니어링(Over-engineering)**이 될 수 있으며, 오직 비즈니스 룰이 복잡하고 수명이 긴 엔터프라이즈 코어 시스템에서만 유지보수성이라는 거대한 보상을 가져다주는 트레이드오프입니다.

3. Counterexample

  • 프레임워크 주도 개발 (Framework-Driven Decay): 서비스 로직 안에 Spring Framework의 HttpServletRequest가 날아다니고, 도메인 클래스에 JPA의 @Entity@Table 어노테이션이 덕지덕지 붙어있는 경우. 데이터베이스 스키마를 엎거나(MySQL \rightarrow MongoDB) 웹 프레임워크를 교체해야 할 때, 비즈니스 핵심 로직까지 수만 줄을 뜯어고쳐야 하는 재앙(Vendor Lock-in)에 직면합니다.
  • 가짜 계층형 아키텍처 (Bypass Antipattern): 3계층(Web \rightarrow Service \rightarrow Repository)을 만들었지만, 꼼수를 부려 Web 계층(Controller)이 Service를 무시하고 Repository(DB)를 직접 찔러 데이터를 가져오는 경우. 비즈니스 룰(Service)이 우회당하면서 보안, 권한 검사, 로깅 로직이 모두 누락되는 '싱크홀(Sinkhole)' 안티 패턴이 뚫립니다.

4. Prerequisites

  • 객체 지향 원칙 (Basic): SOLID 원칙, 특히 DIP (의존성 역전 원칙)와 인터페이스. (09-01 Software Engineering)
  • RDBMS 스키마 설계 (Basic): 데이터베이스 계층의 역할 이해. (06-01-01 Relational Modeling)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Layered Architecture 하향식으로 떨어지는 3계층의 흐름과, 결국 맨 밑바닥 DB(Repository)가 전체를 지배하게 되는 모순을 쥡니다. P1
2 The Power of DIP 추상화(Interface)를 컴파일러 뼈대에 삽입하여 의존성 화살표의 방향을 180도 돌려버리는 반격의 기술을 해부합니다. P5
3 Clean Architecture 도메인 엔티티를 가장 안전한 정중앙에 유폐시키고, 외부의 어떤 변화(DB/UI)도 침투하지 못하게 막는 양파 껍질 구조를 뜯어봅니다. Industry
4 Hexagonal (Ports & Adapters) 앱을 육각형 코어로 정의하고, 입출력을 USB 포트(Port)와 어댑터로 규격화하여 테스트 가능성을 폭발시키는 엔지니어링을 장악합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 수직으로 흐르는 데이터, 계층형 아키텍처 (Layered Architecture)

  • Why to Learn: 가장 대중적이고 직관적인 아키텍처 뼈대를 장악하고, 이 구조가 필연적으로 겪게 되는 "데이터베이스 주도 설계"의 치명적 한계를 인지하기 위함입니다.
  • What to Learn:
    • Concepts: Presentation Layer (Web/UI), Business/Service Layer (로직), Data Access/Repository Layer (DB 접근), Top-down Dependency.
    • Skills: 관심사의 분리(SoC)를 적용한 폴더/패키지 구조 설계.
  • How to Learn:
    • 1단계: 관심사의 물리적 격리: 사용자의 HTTP 요청을 파싱하는 코드(Controller), "쿠폰 적용 후 10% 할인"을 계산하는 코드(Service), DB에 INSERT 쿼리를 날리는 코드(Repository)를 별도의 클래스와 계층으로 찢는 분업을 해부합니다.
    • 2단계: DB 지배적 구조의 모순: 화살표가 위에서 아래로 향합니다(Web \rightarrow Service \rightarrow DB). Service 계층은 DB 계층에 강하게 결합됩니다. 결국 "DB 스키마를 어떻게 짤 것인가?"가 비즈니스 로직의 생김새를 강제해 버리는 아키텍처의 비극을 뜯어봅니다.
  • Implement: 파이썬 3계층 장난감 스크립트. Controller 클래스가 Service 인스턴스를 생성하고, ServiceRepository 인스턴스를 직접 생성(Hard-coded Dependency)하여 사용하는 구조. DB 접속 정보를 바꾸면 Service 코드까지 컴파일 에러가 전파되는 취약점 콘솔 렌더링.

Core Topic 02: 화살표를 꺾어라, 의존성 역전 원칙 (The Power of DIP)

  • Why to Learn: 클린/헥사고날 아키텍처의 유일한 무기이자 마법인 DIP(Dependency Inversion Principle)를 이해하지 못하면, 껍데기만 남은 폴더 구조(Cargo Cult)를 짜게 되므로 이 물리적 원리를 해부하기 위함입니다.
  • What to Learn:
    • Concepts: Dependency Inversion Principle (DIP), Inversion of Control (IoC), Interface Abstract, Compile-time Dependency vs Run-time Dependency.
    • Skills: 추상화(Interface)를 통한 클래스 간 강결합 끊기.
  • How to Learn:
    • 1단계: 런타임과 컴파일타임의 불일치: 런타임에는 여전히 ServiceRepository(DB)를 호출합니다. 하지만 컴파일 타임에는 ServiceRepository 인터페이스에 의존하고, 실제 DB 코드가 그 인터페이스를 구현(의존)하게 화살표 방향을 위로 꺾어버리는 물리법칙을 해부합니다.
    • 2단계: 고수준의 오만함: 이제 DB(저수준)가 Service(고수준)의 눈치를 봅니다. Service가 "나는 이런 메서드(saveUser())가 필요해"라고 인터페이스를 던지면, DB는 그 규격에 맞춰 구부려야 합니다. 고수준 로직이 완벽히 보호받는 패러다임 쉬프트를 뜯어봅니다.
  • Implement: DIP 리팩터링 전후 비교 스크립트. 1) Service 클래스가 MySQLRepository 구체 클래스를 직접 의존. 2) 중간에 IRepository 인터페이스를 끼워 넣음. Service는 오직 IRepository만 알고, MySQLRepository가 이를 상속받아 구현. DB를 MongoRepository로 갈아 끼워도 Service 코드는 단 1줄도 수정되지 않는 컴파일 분리 데모.

Practical

Core Topic 03: 양파 껍질 속의 성역, 클린 아키텍처 (Clean Architecture)

  • Why to Learn: 로버트 C. 마틴(Uncle Bob)이 제시한 의존성 규칙(Dependency Rule)을 통해, 웹 프레임워크나 DB 기술이 멸망해도 비즈니스 로직 엔티티는 살아남는 극단의 방어 아키텍처를 세우기 위함입니다.
  • What to Learn:
    • Concepts: Entity (순수 비즈니스 룰), Use Case (애플리케이션 특정 로직), Interface Adapters (컨트롤러/프레젠터), Frameworks & Drivers (Web/DB).
    • Skills: 의존성 규칙(항상 바깥에서 안쪽으로만 향함) 강제화, 데이터 변환 매퍼(Mapper) 작성.
  • How to Learn:
    • 1단계: 동심원의 물리적 경계: 정중앙에는 프레임워크 종속성이 0%인 순수 언어(Java, C#)로만 짜인 Entity와 Use Case가 있습니다. 바깥쪽 원(Controller, DB)은 안쪽 원을 알지만, 안쪽 원은 바깥쪽 원의 존재조차 모르게(화살표가 단방향) 캡슐화하는 경계 짓기를 해부합니다.
    • 2단계: 데이터 변환의 고통(Mapper): DB에서 데이터를 읽어오면 JPA Entity(바깥쪽)가 나옵니다. 이를 도메인 Entity(안쪽)로 변환하고, Use Case가 처리한 뒤, 이를 다시 DTO(바깥쪽 UI용)로 변환해야 경계를 넘을 수 있습니다. 무수한 변환 오버헤드라는 방화벽의 대가를 뜯어봅니다.
  • Implement: 프레임워크 락인(Lock-in) 방어 데모. core 폴더 안에는 외부 라이브러리 import가 단 하나도 없는 순수 파이썬 클래스(User, CreateUserUseCase)만 둠. delivery 폴더(FastAPI)와 infrastructure 폴더(SQLAlchemy)가 core를 감싸고 통신하는 3단계 양파 껍질 디렉토리 뼈대 스크립트화.

Advanced

Core Topic 04: USB 포트와 어댑터 플러그인, 헥사고날 아키텍처 (Ports & Adapters)

  • Why to Learn: 클린 아키텍처를 실무 백엔드 엔지니어링에 가장 잘 적용할 수 있는 형태로 개량한 '포트와 어댑터' 패러다임을 통해, 완벽한 TDD(테스트 주도 개발) 인프라를 구축하기 위함입니다.
  • What to Learn:
    • Concepts: Hexagonal Architecture, Inbound Port (Driving), Outbound Port (Driven), Inbound Adapter (Web/CLI), Outbound Adapter (DB/External API).
    • Skills: 포트를 모의 객체(Mock/Stub) 어댑터로 교체하여 DB나 Web 서버 없이 비즈니스 로직 100% 커버리지 유닛 테스트 작성.
  • How to Learn:
    • 1단계: 육각형 코어와 포트: 애플리케이션 코어(육각형)의 모든 진출입로는 'Port(인터페이스)'로만 규정됩니다. 클라이언트가 들어오는 곳은 Inbound Port(주문하기 인터페이스), DB로 나가는 곳은 Outbound Port(주문저장 인터페이스)로 물리적 구멍을 뚫어놓는 설계를 해부합니다.
    • 2단계: 어댑터 플러그인: Inbound Port에는 REST API 어댑터나 gRPC 어댑터를 꽂을 수 있고, Outbound Port에는 MySQL 어댑터나 MongoDB 어댑터를 꽂을 수 있습니다. 심지어 테스트를 할 때는 '메모리 어댑터(Mock)'를 꽂아 0.1초 만에 테스트를 끝내버리는 플러그인 아키텍처의 궁극적 위력을 뜯어봅니다.
  • Implement: 헥사고날 테스트 하네스 장난감. OrderCoreIPaymentPort를 요구함. 실서비스용 StripeAdapter 대신, MockPaymentAdapter(항상 성공 반환)를 꽂아넣고 코어를 실행. 외부 네트워크(결제 PG사) 없이 코어 비즈니스 로직(할인율 10% 적용)만 1밀리초 만에 Assert(검증)하는 격리된 TDD 데모 스크립트.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Layered Architecture 애플리케이션을 역할에 따라 Web, Service, Repository 등의 수평적 계층으로 쌓아 올리고 상위 계층이 하위 계층을 단방향으로 호출하는 가장 기본적인 구조입니다. 기본 관심사의 분리(SoC) MVC Pattern Clean Architecture 설계가 직관적이지만 필연적으로 DB(Repository) 주도 설계의 늪에 빠지기 쉬움 P1:CS2023 core
Dependency Inversion Principle (DIP) 상위 모듈이 하위 모듈에 의존하지 않도록, 중간에 인터페이스(추상화)를 삽입하여 컴파일 타임의 의존성 방향을 역전시키는 객체지향의 마법입니다. 권장 결합도 제거 Interface / SOLID Tight Coupling 실행 흐름(Runtime)이 역전되는 것이 아니라 소스 코드 의존성(Compile-time)이 역전되는 것임 P5:SFIA core
Clean Architecture 도메인 엔티티(핵심 비즈니스 규칙)를 정중앙에 배치하고, 바깥쪽 계층(DB, UI)은 안쪽 계층에만 의존할 수 있도록 '의존성 규칙'을 강제하는 동심원 아키텍처입니다. 심화 프레임워크 독립성 Use Case / Entity Layered Architecture 폴더 구조를 복잡하게 만드는 것이 목적이 아니라 프레임워크 격리가 목적임 Industry core
Hexagonal Architecture 애플리케이션 코어(육각형)의 입출력을 포트(Port) 인터페이스로 정의하고, Web이나 DB 기술을 어댑터(Adapter) 형태로 플러그인처럼 갈아 끼우는 실무적 아키텍처입니다. 실무 외부 시스템 격리 Ports & Adapters Clean Architecture 왜 하필 '육각형'이냐 하면, 입출력 포트가 상하 2개가 아니라 N개(사방)로 존재할 수 있음을 묘사한 것임 Industry core

8. References

Primary

  • [P1] CS2023 - Software Engineering (SE) - Software Architecture (Architectural Patterns)
  • [P5] SFIA - Software Design (SWDN) - Design Principles (SOLID)

Secondary

  • [Clean Architecture] Robert C. Martin - Dependency Rule, Entities, and Use Cases
  • [Implementing Domain-Driven Design] Vaughn Vernon - Hexagonal Architecture and Ports/Adapters

Industry

  • [Martin Fowler's Bliki] - PresentationDomainDataLayering
  • [Netflix TechBlog] - Ready for changes with Hexagonal Architecture

9. Final Checklist

Primary

  • 전통적인 3계층(Layered) 구조에서 화살표(의존성)가 위에서 아래로 향할 때, 하위 계층(DB)의 변경이 상위 계층(로직)에 연쇄적인 파급 효과(Ripple Effect)를 일으키는 현상을 설명할 수 있는가?
  • DIP(의존성 역전 원칙)를 적용하여 중간에 인터페이스(Interface)를 삽입하면, 소스 코드 레벨의 의존성 화살표가 어떻게 180도 역전되는지 물리적으로 증명할 수 있는가?

Secondary

  • 클린 아키텍처에서 바깥쪽 원(Framework)이 안쪽 원(Entity)을 참조할 수는 있지만, 안쪽 원은 바깥쪽 원의 존재조차 몰라야 하는 단방향 의존성 규칙(Dependency Rule)을 묘사할 수 있는가?
  • 데이터베이스에서 조회한 JPA Entity(DB 모델)를 도메인 계층의 순수 Domain Entity로 변환(Mapping)하는 과정이 왜 필수적이며, 이로 인해 발생하는 오버헤드를 아키텍처 관점에서 어떻게 저울질할 수 있는가?

Industry

  • 헥사고날 아키텍처에서 Inbound Port(Driving)와 Outbound Port(Driven)의 차이를 구별하고, Web Controller와 DB Repository가 각각 어떤 어댑터(Adapter)로 연결되는지 해부할 수 있는가?
  • 비즈니스 로직(육각형 코어)의 단위 테스트(Unit Test)를 작성할 때, 느린 진짜 DB 대신 메모리 기반의 Mock(가짜) 어댑터를 Outbound Port에 갈아 끼워 테스트 속도를 극대화하는 설계를 할 수 있는가?

System Architecture · Architectural Evolution

3 / 5