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
| Benefit | Description |
|---|---|
| Testability | Easy to mock adapters for testing |
| Flexibility | Swap adapters without changing core |
| Maintainability | Clear separation of concerns |
| Framework Independence | Core is not tied to any framework |
| Team Scalability | Different 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
| Aspect | Hexagonal | Clean Architecture |
|---|---|---|
| Focus | Ports & Adapters | Layer dependencies |
| Structure | Hexagonal layers | Circular layers |
| Domain | Pure domain | Domain + Application |
| Complexity | Medium | High |