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

Clean Architecture

Overview

Clean Architecture là một architectural style tập trung vào việc tách code thành các layers có dependencies chỉ hướng vào trong (inward direction). Mục tiêu là tạo ra codebase dễ test, maintain, và independent với frameworks, databases, UI.

Layer Structure

┌─────────────────────────────────────┐
│     Presentation (API, UI)          │  ← Outer Layer
├─────────────────────────────────────┤
│     Application (Use Cases)         │
├─────────────────────────────────────┤
│     Domain (Entities, Business)     │  ← Inner Layer (No dependencies)
├─────────────────────────────────────┤
│     Infrastructure (DB, External)   │
└─────────────────────────────────────┘

1. Domain Layer (Innermost)

  • Entities: Business objects với identity
  • Value Objects: Immutable objects without identity
  • Domain Services: Business logic không thuộc về entities
  • Repository Interfaces: Contracts (không implementations)
// Domain/Entities/Order.cs
public class Order
{
    public Guid Id { get; private set; }
    public List<OrderItem> Items { get; private set; }
    public OrderStatus Status { get; private set; }
    
    public void AddItem(Product product, int quantity)
    {
        // Business logic here
        Items.Add(new OrderItem(product, quantity));
    }
    
    public void Place()
    {
        // Domain rules
        if (Items.Count == 0)
            throw new InvalidOperationException("Cannot place empty order");
        Status = OrderStatus.Placed;
    }
}

// Domain/Repositories/IOrderRepository.cs
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(Guid id);
    Task AddAsync(Order order);
    Task UpdateAsync(Order order);
}

2. Application Layer

  • Use Cases / Services: Orchestrate business logic
  • DTOs: Data transfer objects
  • Interfaces: Ports for external services
// Application/UseCases/PlaceOrderUseCase.cs
public class PlaceOrderUseCase
{
    private readonly IOrderRepository _orderRepository;
    private readonly IInventoryService _inventoryService;
    private readonly INotificationService _notificationService;
    
    public PlaceOrderUseCase(
        IOrderRepository orderRepository,
        IInventoryService inventoryService,
        INotificationService notificationService)
    {
        _orderRepository = orderRepository;
        _inventoryService = inventoryService;
        _notificationService = notificationService;
    }
    
    public async Task ExecuteAsync(PlaceOrderCommand command)
    {
        var order = new Order();
        
        foreach (var item in command.Items)
        {
            var product = await _inventoryService.GetProductAsync(item.ProductId);
            order.AddItem(product, item.Quantity);
        }
        
        order.Place();
        await _orderRepository.AddAsync(order);
        
        await _notificationService.SendAsync(order.CustomerEmail, "Order placed!");
    }
}

3. Infrastructure Layer

  • Repository Implementations: Concrete implementations
  • External Services: APIs, Email, Cache
  • Database: Entity Framework, Dapper
// Infrastructure/Persistence/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;
    
    public OrderRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public async Task<Order> GetByIdAsync(Guid id)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id);
    }
    
    public async Task AddAsync(Order order)
    {
        await _context.Orders.AddAsync(order);
        await _context.SaveChangesAsync();
    }
}

4. Presentation Layer

  • API Controllers: HTTP endpoints
  • View Models: UI models
// Presentation/Controllers/OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly PlaceOrderUseCase _placeOrderUseCase;
    
    public OrdersController(PlaceOrderUseCase placeOrderUseCase)
    {
        _placeOrderUseCase = placeOrderUseCase;
    }
    
    [HttpPost]
    public async Task<IActionResult> PlaceOrder([FromBody] PlaceOrderCommand command)
    {
        await _placeOrderUseCase.ExecuteAsync(command);
        return Ok();
    }
}

Key Principles

1. Dependency Rule

  • Dependencies chỉ được đi từ outer layers vào inner layers
  • Inner layers không biết gì về outer layers
  • Domain layer là hoàn toàn independent

2. Separation of Concerns

  • Mỗi layer chỉ quan tâm đến một responsibility
  • Business logic trong Domain layer
  • Orchestration trong Application layer

3. Testability

  • Domain layer có thể test không cần database
  • Use cases có thể test với mocks
// Testing Domain Layer
[Fact]
public void Order_Place_WithNoItems_ThrowsException()
{
    var order = new Order();
    
    Assert.Throws<InvalidOperationException>(() => order.Place());
}

Benefits

BenefitDescription
TestabilityEasy to unit test business logic
MaintainabilityClear structure, easy to navigate
IndependenceNot tied to frameworks or databases
Business FocusDomain logic is central and clear
FlexibilityEasy to change UI or infrastructure

Comparison with Other Patterns

PatternFocusComplexity
Clean ArchitectureBusiness logic independenceHigh
Hexagonal ArchitecturePort/Adapter separationMedium
Onion ArchitectureLayered dependenciesMedium
Layered ArchitectureTraditional N-tierLow

When to Use

  • Large enterprise applications
  • Complex business logic
  • Long-lived projects
  • Teams that need clear structure
  • Projects requiring testability

References