Hexagonal Architecture와 Clean Architecture, 무엇이 같고 무엇이 다를까

발행일: Jul, 2026
조회수: 0
단어수: 1119

목차

Clean Architecture를 공부하다 보면 Hexagonal Architecture라는 이름을 자연스럽게 만나게 됩니다. 그리고 처음에는 조금 혼란스럽습니다. 둘 다 도메인을 중심에 두고, 외부 기술에 대한 의존을 줄이고, 의존성 역전을 이야기하기 때문입니다. 그렇다면 둘은 같은 것일까요? 결론부터 말하면 비슷한 철학을 공유하지만, 바라보는 방향이 다릅니다.

화이트보드에 Clean Architecture의 동심원 다이어그램과 Hexagonal Architecture의 포트·어댑터 다이어그램이 나란히 그려져 있고, 그 아래에 두 아키텍처의 공통점이 정리되어 있는 그림

Hexagonal Architecture란

Hexagonal Architecture는 Alistair Cockburn이 제안한 패턴으로, Ports and Adapters Architecture라고도 부릅니다. 핵심 아이디어는 한 문장으로 요약됩니다.

애플리케이션의 핵심 로직을 외부 세계로부터 분리하자.

애플리케이션의 한가운데에는 비즈니스 로직이 있습니다. 그리고 그 바깥에는 REST API, 메시지 큐, 데이터베이스 같은 다양한 외부 시스템이 있습니다. 이 둘 사이에 Port를 두고, Adapter로 연결합니다.

REST API CLI Adapter Adapter Inbound Port Application Domain Outbound Port Database Adapter Message Adapter
그림 1. Ports and Adapters — 핵심은 육각형이라는 모양이 아니라, 안과 밖 사이에 Port라는 접점이 놓여 있다는 점입니다.

Port

Port는 애플리케이션과 외부 세계 사이의 접점(interface)입니다. 주문을 저장하는 기능이 있다고 해보겠습니다.

public interface IOrderRepository
{
    Task Save(Order order);
}

이 인터페이스가 Outbound Port의 역할을 합니다. 애플리케이션은 저장소가 SQL Server인지 PostgreSQL인지 알 필요가 없습니다. 그저 IOrderRepository라는 Port를 통해 주문을 저장하면 됩니다.

Adapter

Adapter는 실제 기술과 Port를 연결합니다.

public class SqlOrderRepository : IOrderRepository
{
    public async Task Save(Order order)
    {
        // SQL Server에 저장
    }
}

나중에 MongoDB로 옮기더라도 Domain이나 Application 코드는 그대로 둘 수 있습니다. 구현이 하나 더 늘어날 뿐, Port를 바라보는 쪽은 변하지 않습니다.

            Application
                 │
                 ▼
          IOrderRepository
             ▲          ▲
             │          │
       SqlAdapter    MongoAdapter
             │          │
        SQL Server    MongoDB

이것이 Hexagonal Architecture가 강조하는 전부입니다. 외부 세계와 애플리케이션 사이에 Port를 두고, Adapter를 통해 연결한다.

Clean Architecture란

Clean Architecture는 Robert C. Martin이 정리한 아키텍처입니다. 핵심은 Dependency Rule 하나로 압축됩니다.

소스 코드의 의존성은 항상 안쪽을 향해야 한다.

보통 동심원으로 그립니다. 하지만 여기서 중요한 것은 원의 모양이 아니라 화살표의 방향입니다.

Frameworks & Drivers Interface Adapters Use Cases Entities 의존성 Framework Adapter Use Case Entity 바깥은 안쪽을 알아도 되지만, 안쪽은 바깥을 알아서는 안 됩니다
그림 2. Dependency Rule — 원의 개수나 이름은 프로젝트마다 달라도 됩니다. 바뀌면 안 되는 것은 화살표가 한쪽만 향한다는 사실입니다.

예를 들어 Domain Entity가 특정 ORM을 직접 참조해서는 안 됩니다.

❌  Domain ──────► Entity Framework

⭕  Entity Framework ──────► Domain

즉 Clean Architecture는 의존성의 방향을 통제하는 것에 가장 큰 의미를 둡니다.

둘이 공유하는 것

이름은 다르지만 두 아키텍처가 공유하는 지점은 생각보다 넓습니다.

도메인이 중심이다

둘 다 비즈니스 로직을 외부 기술보다 위에 둡니다. 데이터베이스가 바뀌어도 비즈니스 로직이 흔들려서는 안 되고, REST API가 GraphQL로 바뀌어도 핵심 규칙은 그대로여야 합니다.

기술이 비즈니스 로직을 결정하는 것이 아니라, 비즈니스 로직이 기술로부터 독립되어야 한다.

Dependency Inversion

두 아키텍처 모두 의존성 역전 원칙(DIP)을 도구로 씁니다. 나쁜 구조는 이렇게 생겼습니다.

OrderService ──► SqlOrderRepository ──► SQL Server

OrderService가 구체적인 SQL Repository를 직접 알고 있습니다. 데이터베이스 기술이 바뀌면 애플리케이션 코드까지 영향을 받습니다. 사이에 인터페이스를 하나 두면 화살표 하나가 뒤집힙니다.

Before — 구현을 직접 안다 OrderService SqlOrderRepository After — 둘 다 추상화를 향한다 OrderService IOrderRepository Sql / Mongo 구현이 인터페이스를 향해 거꾸로 올라옵니다
그림 3. 의존성 역전 — 화살표 하나의 방향을 바꾸는 것이 전부입니다. 그런데 그 하나로 "누가 누구를 갈아끼울 수 있는가"가 뒤집힙니다.
public class OrderService
{
    private readonly IOrderRepository repository;

    public OrderService(IOrderRepository repository)
    {
        this.repository = repository;
    }
}

실제 구현은 바깥에 있으면서도, 애플리케이션은 추상화에만 의존하게 됩니다.

Framework와 Database로부터의 독립

또 하나의 공통점은 프레임워크와 데이터베이스를 중심에 두지 않는다는 것입니다. 다음 코드를 보겠습니다.

public class OrderService
{
    private readonly DbContext context;   // 기술이 Application 안으로 들어왔습니다

    public async Task CreateOrder(...)
    {
        // ...
    }
}

Application Layer가 DbContext를 직접 쓰고 있습니다. 이 구조에서는 데이터베이스 기술이 애플리케이션 계층까지 침투합니다. 반면 추상화를 두면 이렇게 됩니다.

public class CreateOrderHandler
{
    private readonly IOrderRepository repository;

    public CreateOrderHandler(IOrderRepository repository)
    {
        this.repository = repository;
    }
}

애플리케이션은 데이터가 어디에 저장되는지 알 필요가 없어집니다.

여기까지는 두 아키텍처가 거의 같은 말을 합니다. 실제로 코드만 보고 "이건 Hexagonal이고 저건 Clean"이라고 구분하기 어려운 경우가 많은 이유입니다.

그렇다면 차이는 무엇일까

여기서부터가 중요합니다. 둘은 비슷한 구조를 만들어낼 수 있지만 출발점이 다릅니다.

Hexagonal Architecture는 "외부와 어떻게 연결할 것인가"에 관심이 있습니다.
Clean Architecture는 "의존성을 어떻게 관리할 것인가"에 관심이 있습니다.

Hexagonal은 Port와 Adapter에 집중한다

Hexagonal Architecture에서 중요한 것은 경계(boundary)입니다. 애플리케이션의 안과 밖을 나누고 그 사이에 Port를 둡니다. 외부와 통신하는 방법이 늘어나면 Adapter를 하나 더 만들면 됩니다.

REST Controller  ─┐
Message Consumer ─┼─►  Application  ─►  Database Adapter
CLI Adapter      ─┘

즉 이런 질문을 중심에 둡니다. 애플리케이션의 핵심을 외부의 변화로부터 어떻게 보호할 것인가.

Clean은 Dependency Rule에 집중한다

Clean Architecture는 어떤 코드가 어떤 코드를 참조해도 되는지, 그 방향 자체를 규칙으로 못박습니다.

Infrastructure ──────► Application ──────► Domain

즉 이런 질문을 중심에 둡니다. 어떤 코드가 어떤 코드에 의존할 수 있는가.

같은 시스템, 두 개의 관점

재미있는 점은 동일한 주문 시스템을 두 관점으로 모두 설명할 수 있다는 것입니다. 다음과 같은 시스템이 있다고 해보겠습니다.

REST API Message Queue Adapter Adapter CreateOrder Order Domain IOrderRepository Database Hexagonal: 경계는 어디인가 Clean: 화살표는 어디로
그림 4. 그림은 하나인데 읽는 방법이 두 가지입니다. 왼쪽으로 읽으면 경계 이야기가 되고, 오른쪽으로 읽으면 의존성 이야기가 됩니다.

Hexagonal 관점에서는 이렇게 설명합니다.

REST API와 Message Queue는 Adapter이고, 애플리케이션과 외부 시스템 사이에는 Port가 있다.

Clean Architecture 관점에서는 이렇게 설명합니다.

Database와 Framework에 대한 의존성은 바깥쪽에 있고, Domain과 Use Case는 외부 구현에 의존하지 않는다.

같은 코드를 서로 다른 관점으로 설명하고 있을 뿐입니다.

실제 프로젝트에서는 함께 쓴다

그래서 실무에서는 굳이 둘 중 하나를 고를 필요가 없습니다. 오히려 이런 모습이 흔합니다.

Infrastructure REST Adapter DB Adapter Port (In) Port (Out) Application Use Cases Domain Entities
그림 5. 두 아이디어가 한 그림 안에 같이 들어 있습니다. 안쪽으로 향하는 의존성은 Clean이, 안과 밖을 가르는 Port는 Hexagonal이 준 것입니다.

Clean Architecture는 의존성이 안쪽을 향한다고 말하고, Hexagonal Architecture는 외부 시스템과 Application 사이에 Adapter와 Port가 있다고 말합니다. 둘은 충돌하지 않습니다. 오히려 잘 어울립니다.

Layer냐 Feature냐는 또 다른 축

여기서 한 단계 더 생각해볼 것이 있습니다. 두 아키텍처를 이야기하다 보면 자연스럽게 이런 폴더 구조가 나옵니다.

Controllers/
Services/
Repositories/
Entities/

하지만 이것은 아키텍처의 원칙과 폴더 구조를 혼동한 것일 수 있습니다. 같은 원칙을 지키면서도 이런 구조를 만들 수 있기 때문입니다.

Features/
├── Orders/
│   ├── CreateOrder/
│   │   ├── Endpoint.cs
│   │   ├── Handler.cs
│   │   ├── Validator.cs
│   │   └── Repository.cs
│   │
│   ├── CancelOrder/
│   │   ├── Endpoint.cs
│   │   ├── Handler.cs
│   │   └── Repository.cs
│   │
│   └── PayOrder/
│       └── ...
│
└── Users/
    └── ...

이 구조에서도 Clean Architecture의 Dependency Rule을 지킬 수 있고, Hexagonal Architecture의 Port와 Adapter 개념도 그대로 적용됩니다. Vertical Slice를 조금 더 자세히 다룬 글은 Clean Architecture의 Vertical Slice와 Horizontal Slice에 정리해 두었습니다.

정리하면 세 개념은 서로 대체 관계가 아닙니다. 애초에 다른 질문에 답하고 있기 때문입니다.

개념 핵심 질문 한 단어로
Clean Architecture 의존성은 어느 방향으로 향해야 하는가 Dependency
Hexagonal Architecture 외부 시스템과 어떻게 분리하고 연결할 것인가 Boundary
Vertical Slice 코드를 어떤 기능 단위로 구성할 것인가 Feature

이 세 가지를 서로 다른 축으로 놓고 보면 논쟁이 대부분 사라집니다. "Clean이냐 Hexagonal이냐"는 질문은 사실 "세로축이냐 가로축이냐"를 묻는 것과 비슷합니다.

그래서 무엇을 선택해야 할까

사실 이 질문 자체가 조금 어긋나 있습니다. "Clean과 Hexagonal 중 무엇을 쓸까"보다는 "우리 시스템에서 지금 아픈 곳이 어디인가"를 묻는 편이 낫습니다.

  • 비즈니스 로직이 데이터베이스나 프레임워크에 강하게 붙어 있다 → Dependency Rule
  • 외부 시스템이 많고 API·메시지 큐·저장소를 교체할 가능성이 있다 → Port & Adapter
  • 기능이 늘어나면서 Service 하나가 계속 비대해진다 → Vertical Slice

그리고 세 가지를 동시에 적용해도 아무 문제가 없습니다.

반대로 아픈 곳이 없는데 먼저 구조부터 세우는 것은 대체로 손해입니다. 인터페이스와 폴더가 늘어나면 그만큼 읽어야 할 코드도 늘어납니다. 아키텍처는 지금 겪고 있는 변경의 고통에 대한 대응이지, 미래에 대한 보험으로는 생각보다 잘 작동하지 않습니다.

마무리

Hexagonal Architecture와 Clean Architecture는 완전히 다른 아키텍처라기보다, 비슷한 철학을 서로 다른 각도에서 바라보는 방법에 가깝습니다. 둘 모두 다음을 중요하게 생각합니다.

  • 도메인을 중심에 둔다
  • 비즈니스 로직을 기술적 세부사항으로부터 보호한다
  • 의존성 역전을 적극적으로 활용한다
  • 데이터베이스와 프레임워크의 변경이 핵심 로직에 미치는 영향을 최소화한다

다만 강조점이 다릅니다. Hexagonal은 "외부와의 경계를 어떻게 만들 것인가"를 묻고, Clean은 "의존성의 방향을 어떻게 통제할 것인가"를 묻습니다. 여기에 Vertical Slice를 더하면 "변경되는 기능을 어떻게 하나의 단위로 묶을 것인가"까지 다룰 수 있습니다.

결국 중요한 것은 특정 아키텍처의 이름을 그대로 옮겨 심는 일이 아닙니다. 비즈니스 로직을 외부 기술로부터 보호하고, 변경의 영향을 줄이고, 코드가 비즈니스의 구조를 자연스럽게 드러내도록 만드는 것. 이 아키텍처들이 공통적으로 향하고 있는 곳은 거기라고 생각합니다.

Table of Contents

Study Clean Architecture for a while and you will run into the name Hexagonal Architecture. At first it is a little confusing, because both put the domain at the centre, both reduce dependence on external technology, and both talk about inverting dependencies. So are they the same thing? The short answer: they share a philosophy, but they look at it from different directions.

A whiteboard showing the concentric-circle diagram of Clean Architecture next to the ports-and-adapters diagram of Hexagonal Architecture, with the two architectures' shared traits listed underneath

What Hexagonal Architecture is

Hexagonal Architecture is a pattern proposed by Alistair Cockburn, also known as Ports and Adapters Architecture. The core idea fits in one sentence.

Separate the core logic of the application from the outside world.

At the centre of the application sits the business logic. Outside it sit various external systems: a REST API, a message queue, a database. Between the two you place a port, and you connect through an adapter.

REST API CLI Adapter Adapter Inbound Port Application Domain Outbound Port Database Adapter Message Adapter
Figure 1. Ports and adapters — what matters is not the hexagon shape but the fact that a port sits between the inside and the outside.

Port

A port is the interface between the application and the outside world. Say we have a feature that saves an order.

public interface IOrderRepository
{
    Task Save(Order order);
}

That interface acts as an outbound port. The application does not need to know whether the store is SQL Server or PostgreSQL. It just saves the order through the IOrderRepository port.

Adapter

An adapter connects the port to the actual technology.

public class SqlOrderRepository : IOrderRepository
{
    public async Task Save(Order order)
    {
        // save to SQL Server
    }
}

If you later move to MongoDB, the domain and application code can stay exactly as it is. There is one more implementation; nothing that looks at the port changes.

            Application
                 │
                 ▼
          IOrderRepository
             ▲          ▲
             │          │
       SqlAdapter    MongoAdapter
             │          │
        SQL Server    MongoDB

That is the whole of what Hexagonal Architecture emphasises. Put a port between the outside world and the application, and connect through an adapter.

What Clean Architecture is

Clean Architecture is the architecture set out by Robert C. Martin. Its core compresses into a single Dependency Rule.

Source code dependencies must always point inward.

It is usually drawn as concentric circles. But what matters here is not the shape of the circles, it is the direction of the arrow.

Frameworks & Drivers Interface Adapters Use Cases Entities Dependency Framework Adapter Use Case Entity The outside may know the inside; the inside must not know the outside
Figure 2. The Dependency Rule — the number of rings and their names can differ per project. What must not change is that the arrow only points one way.

A domain entity, for example, must not reference a specific ORM directly.

❌  Domain ──────► Entity Framework

⭕  Entity Framework ──────► Domain

So Clean Architecture places its greatest weight on controlling the direction of dependencies.

What the two share

The names differ, but the overlap between the two is wider than it first appears.

The domain is the centre

Both place business logic above external technology. A change of database must not shake the business logic, and swapping a REST API for GraphQL must leave the core rules untouched.

Technology should not decide the business logic; the business logic should be independent of the technology.

Dependency inversion

Both architectures use the Dependency Inversion Principle as a tool. The bad structure looks like this.

OrderService ──► SqlOrderRepository ──► SQL Server

OrderService knows the concrete SQL repository directly. Change the database technology and the application code is affected. Put an interface in between and one arrow flips.

Before — knows the implementation OrderService SqlOrderRepository After — both point at the abstraction OrderService IOrderRepository Sql / Mongo the implementation now points up at the interface
Figure 3. Dependency inversion — all you do is turn one arrow around. And that one turn flips who can be swapped out.
public class OrderService
{
    private readonly IOrderRepository repository;

    public OrderService(IOrderRepository repository)
    {
        this.repository = repository;
    }
}

The real implementation stays on the outside, while the application depends only on the abstraction.

Independence from frameworks and databases

The other thing they share is refusing to put the framework and the database at the centre. Look at this code.

public class OrderService
{
    private readonly DbContext context;   // the technology has come inside the application

    public async Task CreateOrder(...)
    {
        // ...
    }
}

The application layer is using DbContext directly. In this structure the database technology seeps into the application layer. Put an abstraction in and it becomes this instead.

public class CreateOrderHandler
{
    private readonly IOrderRepository repository;

    public CreateOrderHandler(IOrderRepository repository)
    {
        this.repository = repository;
    }
}

The application no longer needs to know where the data is stored.

Up to this point the two architectures say almost the same thing. That is why it is often hard to look at code alone and declare "this one is hexagonal and that one is clean".

So where do they differ?

This is the important part. They can produce similar structures, but they start from different places.

Hexagonal Architecture cares about "how do we connect to the outside?"
Clean Architecture cares about "how do we manage dependencies?"

Hexagonal focuses on ports and adapters

What matters in Hexagonal Architecture is the boundary. You separate the inside of the application from the outside and put a port in between. If there is another way to talk to the outside, you add another adapter.

REST Controller  ─┐
Message Consumer ─┼─►  Application  ─►  Database Adapter
CLI Adapter      ─┘

In other words, it puts this question at the centre. How do we protect the core of the application from changes outside it?

Clean focuses on the Dependency Rule

Clean Architecture nails down, as a rule, which code is allowed to reference which.

Infrastructure ──────► Application ──────► Domain

In other words, it puts this question at the centre. Which code is allowed to depend on which?

One system, two points of view

The interesting part is that the same order system can be described from both angles. Suppose we have this system.

REST API Message Queue Adapter Adapter CreateOrder Order Domain IOrderRepository Database Hexagonal: where is the boundary Clean: which way the arrow
Figure 4. One picture, two ways to read it. Read it leftward and it is a story about boundaries; read it rightward and it is a story about dependencies.

From the hexagonal point of view you would say:

REST API and Message Queue are adapters, and there are ports between the application and the external systems.

From the Clean Architecture point of view you would say:

Dependencies on the database and the framework live on the outside, and the domain and use cases do not depend on external implementations.

It is the same code, described from two different angles.

Real projects use both

Which is why, in practice, you do not have to choose. This is a far more common shape.

Infrastructure REST Adapter DB Adapter Port (In) Port (Out) Application Use Cases Domain Entities
Figure 5. Both ideas live in one picture. The inward-pointing dependency comes from Clean; the port that divides inside from outside comes from Hexagonal.

Clean Architecture says dependencies point inward, and Hexagonal Architecture says adapters and ports sit between the external systems and the application. They do not conflict. If anything, they fit together well.

Layer versus feature is a different axis

There is one more step worth taking. Talk about these two architectures long enough and this folder structure appears on its own.

Controllers/
Services/
Repositories/
Entities/

But that may be confusing the principle with the folder layout, because you can honour the same principles and still build this.

Features/
├── Orders/
│   ├── CreateOrder/
│   │   ├── Endpoint.cs
│   │   ├── Handler.cs
│   │   ├── Validator.cs
│   │   └── Repository.cs
│   │
│   ├── CancelOrder/
│   │   ├── Endpoint.cs
│   │   ├── Handler.cs
│   │   └── Repository.cs
│   │
│   └── PayOrder/
│       └── ...
│
└── Users/
    └── ...

This structure can keep Clean Architecture's Dependency Rule, and Hexagonal Architecture's ports and adapters apply just as well. I covered vertical slices in more depth in Vertical and Horizontal Slices in Clean Architecture.

So the three concepts do not replace one another. They were answering different questions to begin with.

Concept Central question In one word
Clean Architecture Which way should dependencies point? Dependency
Hexagonal Architecture How do we separate from, and connect to, external systems? Boundary
Vertical Slice What unit of functionality do we organise code around? Feature

Put the three on separate axes and most of the argument disappears. Asking "Clean or Hexagonal?" is a bit like asking "vertical axis or horizontal axis?".

So which one should you pick?

The question itself is slightly off. Rather than "Clean or Hexagonal?", the better question is "where does our system actually hurt right now?"

  • Business logic is welded to the database or the framework → Dependency Rule
  • Many external systems, and the API, message queue or store may be swapped → ports and adapters
  • Features keep piling up and one service keeps growing → vertical slices

And applying all three at once is perfectly fine.

Conversely, building structure before anything hurts is usually a net loss. More interfaces and more folders mean more code to read. Architecture is a response to the pain of change you are feeling now; as insurance against the future it works less well than people expect.

Closing

Hexagonal Architecture and Clean Architecture are less two different architectures than two ways of looking at a similar philosophy. Both care about the same things.

  • Put the domain at the centre
  • Protect business logic from technical detail
  • Use dependency inversion deliberately
  • Minimise the effect that database and framework changes have on core logic

What differs is the emphasis. Hexagonal asks "how do we build the boundary with the outside?" and Clean asks "how do we control the direction of dependencies?" Add vertical slices and you can also address "how do we bundle a changing feature into a single unit?"

In the end the point is not to transplant the name of a particular architecture. Protect the business logic from external technology, reduce the blast radius of change, and let the code reveal the shape of the business naturally. That, I think, is where all of these architectures are heading.

태그#Hexagonal Architecture#Clean Architecture#Ports and Adapters#포트와 어댑터#Dependency Rule#의존성 역전#DIP#Vertical Slice#아키텍처#C##.NET

Architecture > 이 카테고리의 다른 포스팅

JUNA BLOG VISITORS

Today0
Total0