Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hexagonal Architecture

Overview

Hexagonal Architecture (còn gọi là Ports and Adapters) là một architectural pattern tập trung vào việc tách biệt application core khỏi các external concerns như databases, UI, và external services. Mục tiêu là tạo ra application có thể test được và independent với infrastructure.

Core Concept

                    ┌──────────────────┐
                    │   Application    │
                    │      Core        │
                    │  (Domain Logic)  │
                    └────────┬─────────┘
                             │
           ┌─────────────────┼─────────────────┐
           │                 │                 │
           ▼                 ▼                 ▼
    ┌────────────┐   ┌────────────┐   ┌────────────┐
    │   Ports    │   │   Ports    │   │   Ports    │
    │ (Input)    │   │ (Output)   │   │ (Output)   │
    └─────┬──────┘   └─────┬──────┘   └─────┬──────┘
          │                │                │
          ▼                ▼                ▼
    ┌────────────┐   ┌────────────┐   ┌────────────┐
    │  Adapters  │   │  Adapters  │   │  Adapters  │
    │ (REST API) │   │ (Database) │   │ (External) │
    └────────────┘   └────────────┘   └────────────┘

Architecture Components

1. Domain (Core)

  • Business entities và rules
  • Không phụ thuộc vào bất kỳ external framework
// Domain/Entities/Product.cs
public class Product
{
    public Guid Id { get; private set; }
    public string Name { get; private set; }
    public Money Price { get; private set; }
    
    public void ApplyDiscount(Percentage discount)
    {
        Price = Price * (1 - discount.Value);
    }
}

// Domain/ValueObjects/Money.cs
public class Money
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    public Money(decimal amount, string currency = "USD")
    {
        Amount = amount;
        Currency = currency;
    }
    
    public static Money operator +(Money a, Money b) 
        => new Money(a.Amount + b.Amount, a.Currency);
    
    public static Money operator *(Money a, decimal factor)
        => new Money(a.Amount * factor, a.Currency);
}

2. Ports (Interfaces)

Ports là contracts định nghĩa cách application giao tiếp với outside world.

// Ports/Input/IOrderService.cs (Primary Port)
public interface IOrderService
{
    Task<OrderResult> CreateOrderAsync(CreateOrderCommand command);
    Task<OrderResult> GetOrderAsync(Guid orderId);
}

// Ports/Output/IOrderRepository.cs (Secondary Port)
public interface IOrderRepository
{
    Task<Order> FindByIdAsync(Guid id);
    Task SaveAsync(Order order);
}

// Ports/Output/INotificationService.cs (Secondary Port)
public interface INotificationService
{
    Task SendAsync(string recipient, string message);
}

3. Adapters (Implementations)

Adapters là implementations của ports, kết nối application với external systems.

// Adapters/Persistence/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
    private readonly DbContext _context;
    
    public OrderRepository(DbContext context)
    {
        _context = context;
    }
    
    public async Task<Order> FindByIdAsync(Guid id)
    {
        return await _context.Orders.FindAsync(id);
    }
    
    public async Task SaveAsync(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
    }
}

// Adapters/Primary/OrderController.cs
public class OrderController : IOrderService
{
    private readonly IOrderService _orderService;
    
    public async Task<OrderResult> CreateOrderAsync(CreateOrderCommand command)
    {
        return await _orderService.CreateOrderAsync(command);
    }
}

4. Application Services

// Application/OrderApplicationService.cs
public class OrderApplicationService : IOrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;
    
    public OrderApplicationService(
        IOrderRepository orderRepository,
        INotificationService notificationService)
    {
        _orderRepository = orderRepository;
        _notificationService = notificationService;
    }
    
    public async Task<OrderResult> CreateOrderAsync(CreateOrderCommand command)
    {
        var order = new Order();
        
        foreach (var item in command.Items)
        {
            order.AddItem(item.ProductId, item.Quantity);
        }
        
        await _orderRepository.SaveAsync(order);
        
        await _notificationService.SendAsync(
            command.CustomerEmail,
            $"Order {order.Id} created successfully");
        
        return new OrderResult(order.Id, order.Total);
    }
}

Dependency Flow

                    ┌─────────────────┐
                    │  Controller/    │
                    │   Consumer      │
                    └────────┬────────┘
                             │ depends on
                             ▼
                    ┌─────────────────┐
                    │     Port        │  ← Interface (Application owns)
                    │   (Input)       │
                    └────────┬────────┘
                             │ implements
                             ▼
                    ┌─────────────────┐
                    │  Application     │
                    │    Service       │
                    └────────┬────────┘
                             │ depends on
                             ▼
                    ┌─────────────────┐
                    │     Port        │  ← Interface (Application owns)
                    │   (Output)      │
                    └────────┬────────┘
                             │ implements
                             ▼
                    ┌─────────────────┐
                    │     Adapter     │  ← Implementation (Infrastructure)
                    │  (Repository)   │
                    └─────────────────┘

Benefits

BenefitDescription
TestabilityEasy to mock adapters for testing
FlexibilitySwap adapters without changing core
MaintainabilityClear separation of concerns
Framework IndependenceCore is not tied to any framework
Team ScalabilityDifferent teams can work on different adapters

Use Cases

  • Complex enterprise applications
  • Applications requiring multiple external integrations
  • Systems that need frequent infrastructure changes
  • Applications with complex testing requirements

Comparison with Clean Architecture

AspectHexagonalClean Architecture
FocusPorts & AdaptersLayer dependencies
StructureHexagonal layersCircular layers
DomainPure domainDomain + Application
ComplexityMediumHigh

References