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.
┌─────────────────────────────────────┐
│ Presentation (API, UI) │ ← Outer Layer
├─────────────────────────────────────┤
│ Application (Use Cases) │
├─────────────────────────────────────┤
│ Domain (Entities, Business) │ ← Inner Layer (No dependencies)
├─────────────────────────────────────┤
│ Infrastructure (DB, External) │
└─────────────────────────────────────┘
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);
}
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!");
}
}
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();
}
}
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();
}
}
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
Mỗi layer chỉ quan tâm đến một responsibility
Business logic trong Domain layer
Orchestration trong Application layer
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());
}
Benefit Description
Testability Easy to unit test business logic
Maintainability Clear structure, easy to navigate
Independence Not tied to frameworks or databases
Business Focus Domain logic is central and clear
Flexibility Easy to change UI or infrastructure
Pattern Focus Complexity
Clean Architecture Business logic independence High
Hexagonal Architecture Port/Adapter separation Medium
Onion Architecture Layered dependencies Medium
Layered Architecture Traditional N-tier Low
Large enterprise applications
Complex business logic
Long-lived projects
Teams that need clear structure
Projects requiring testability