整洁架构 Clean Architecture
大约 8 分钟约 2252 字
整洁架构 Clean Architecture
简介
Clean Architecture(整洁架构)由 Robert C. Martin 提出,核心思想是依赖关系只能从外层指向内层。业务逻辑位于最内层,不依赖任何框架和基础设施。它确保了业务规则的可测试性、框架无关性和长期可维护性。
特点
分层结构
架构层次
┌──────────────────────────────────┐
│ Frameworks & Drivers │ Web、数据库、外部服务
│ ┌──────────────────────────┐ │
│ │ Interface Adapters │ │ Controller、Gateway、Presenter
│ │ ┌──────────────────┐ │ │
│ │ │ Application │ │ │ Use Case、Application Service
│ │ │ Business Rules │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ Enterprise │ │ │ │ Entity、Domain Model
│ │ │ │ Business │ │ │ │
│ │ │ │ Rules │ │ │ │
│ │ │ └──────────────┘ │ │ │
│ │ └──────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────────┘
依赖方向:外 → 内.NET 项目结构
src/
├── Domain/ # 领域层(最内层)
│ ├── Entities/ # 实体和值对象
│ ├── Enums/ # 枚举
│ ├── Events/ # 领域事件
│ ├── Exceptions/ # 领域异常
│ └── Interfaces/ # 仓储接口
│
├── Application/ # 应用层
│ ├── Common/ # 公共:Behaviors、Mappings
│ ├── Interfaces/ # 服务接口
│ ├── Features/ # 功能模块(CQRS)
│ │ ├── Orders/
│ │ │ ├── Commands/
│ │ │ └── Queries/
│ │ └── Products/
│ └── DTOs/ # 数据传输对象
│
├── Infrastructure/ # 基础设施层
│ ├── Persistence/ # 数据库、EF Core
│ ├── Services/ # 外部服务实现
│ ├── Identity/ # 认证实现
│ └── Configuration/ # 配置实现
│
└── WebApi/ # 表现层(最外层)
├── Controllers/
├── Filters/
├── Middleware/
└── Program.csDomain 层 — 领域核心
实体与值对象
/// <summary>
/// Domain 层 — 最内层,不依赖任何外部包
/// </summary>
// 基础实体
public abstract class BaseEntity
{
public Guid Id { get; protected set; } = Guid.NewGuid();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
}
// 订单聚合根
public class Order : BaseEntity
{
public string UserId { get; private set; }
public decimal TotalAmount { get; private set; }
public string Status { get; private set; }
private readonly List<OrderItem> _items = new();
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
private Order() { } // EF Core
public static Order Create(string userId, List<OrderItemData> itemData)
{
var order = new Order
{
UserId = userId,
Status = "Created"
};
foreach (var item in itemData)
{
order.AddItem(item.ProductId, item.ProductName, item.Quantity, item.Price);
}
order.RecalculateTotal();
return order;
}
private void AddItem(int productId, string productName, int quantity, decimal price)
{
var existingItem = _items.FirstOrDefault(i => i.ProductId == productId);
if (existingItem != null)
{
existingItem.IncreaseQuantity(quantity);
}
else
{
_items.Add(new OrderItem(productId, productName, quantity, price));
}
RecalculateTotal();
}
private void RecalculateTotal()
{
TotalAmount = _items.Sum(i => i.Subtotal);
}
// 业务方法
public void Pay(string paymentMethod)
{
if (Status != "Created")
throw new DomainException("只有已创建的订单可以支付");
Status = "Paid";
}
public void Ship(string trackingNumber)
{
if (Status != "Paid")
throw new DomainException("只有已支付的订单可以发货");
Status = "Shipped";
TrackingNumber = trackingNumber;
}
public string? TrackingNumber { get; private set; }
}
// 订单项 — 值对象
public class OrderItem
{
public int ProductId { get; private set; }
public string ProductName { get; private set; }
public int Quantity { get; private set; }
public decimal Price { get; private set; }
public decimal Subtotal => Quantity * Price;
public OrderItem(int productId, string productName, int quantity, decimal price)
{
ProductId = productId;
ProductName = productName;
Quantity = quantity > 0 ? quantity : throw new DomainException("数量必须大于0");
Price = price >= 0 ? price : throw new DomainException("价格不能为负数");
}
public void IncreaseQuantity(int amount)
{
Quantity += amount;
}
}
// 领域异常
public class DomainException : Exception
{
public DomainException(string message) : base(message) { }
}仓储接口
/// <summary>
/// Domain 层定义接口,Infrastructure 层实现
/// </summary>
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<List<Order>> GetByUserIdAsync(string userId, CancellationToken ct = default);
Task<(List<Order> Items, int Total)> GetPagedAsync(int page, int pageSize, CancellationToken ct = default);
Task AddAsync(Order order, CancellationToken ct = default);
Task UpdateAsync(Order order, CancellationToken ct = default);
Task DeleteAsync(Guid id, CancellationToken ct = default);
}
public interface IUnitOfWork : IDisposable
{
Task<int> SaveChangesAsync(CancellationToken ct = default);
}Application 层 — 用例
CQRS 实现
/// <summary>
/// Application 层 — 编排业务用例
/// 引用 Domain 层,不引用 Infrastructure 层
/// </summary>
// 命令
public record CreateOrderCommand(string UserId, List<OrderItemData> Items) : IRequest<Guid>;
// 命令处理器
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>
{
private readonly IOrderRepository _orderRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateOrderCommandHandler(IOrderRepository orderRepository, IUnitOfWork unitOfWork)
{
_orderRepository = orderRepository;
_unitOfWork = unitOfWork;
}
public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken ct)
{
// 使用 Domain 层的工厂方法创建实体
var order = Order.Create(request.UserId, request.Items);
await _orderRepository.AddAsync(order, ct);
await _unitOfWork.SaveChangesAsync(ct);
return order.Id;
}
}
// 查询
public record GetOrderQuery(Guid OrderId) : IRequest<OrderDto?>;
// 查询处理器
public class GetOrderQueryHandler : IRequestHandler<GetOrderQuery, OrderDto?>
{
private readonly IOrderRepository _orderRepository;
public GetOrderQueryHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<OrderDto?> Handle(GetOrderQuery request, CancellationToken ct)
{
var order = await _orderRepository.GetByIdAsync(request.OrderId, ct);
if (order == null) return null;
return new OrderDto(
order.Id,
order.UserId,
order.TotalAmount,
order.Status,
order.Items.Select(i => new OrderItemDto(i.ProductId, i.ProductName, i.Quantity, i.Price)).ToList()
);
}
}Infrastructure 层 — 基础设施
EF Core 实现
/// <summary>
/// Infrastructure 层 — 实现 Domain 层定义的接口
/// </summary>
public class AppDbContext : DbContext, IUnitOfWork
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Orders");
entity.HasKey(e => e.Id);
entity.Property(e => e.UserId).IsRequired();
entity.Property(e => e.Status).IsRequired();
entity.HasMany(e => e.Items).WithOne().HasForeignKey("OrderId");
});
modelBuilder.Entity<OrderItem>(entity =>
{
entity.ToTable("OrderItems");
entity.HasKey("Id");
entity.Property(e => e.ProductName).IsRequired();
});
}
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
// 自动更新审计字段
foreach (var entry in ChangeTracker.Entries<BaseEntity>())
{
if (entry.State == EntityState.Modified)
{
entry.Entity.UpdatedAt = DateTime.UtcNow;
}
}
return await base.SaveChangesAsync(ct);
}
}
// 仓储实现
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context)
{
_context = context;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
}
public async Task<List<Order>> GetByUserIdAsync(string userId, CancellationToken ct = default)
{
return await _context.Orders
.Include(o => o.Items)
.Where(o => o.UserId == userId)
.OrderByDescending(o => o.CreatedAt)
.ToListAsync(ct);
}
public async Task<(List<Order> Items, int Total)> GetPagedAsync(int page, int pageSize, CancellationToken ct = default)
{
var total = await _context.Orders.CountAsync(ct);
var items = await _context.Orders
.Include(o => o.Items)
.OrderByDescending(o => o.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(ct);
return (items, total);
}
public async Task AddAsync(Order order, CancellationToken ct = default)
{
await _context.Orders.AddAsync(order, ct);
}
public Task UpdateAsync(Order order, CancellationToken ct = default)
{
_context.Orders.Update(order);
return Task.CompletedTask;
}
public async Task DeleteAsync(Guid id, CancellationToken ct = default)
{
var order = await GetByIdAsync(id, ct);
if (order != null) _context.Orders.Remove(order);
}
}WebApi 层 — 表现层
DI 注册
/// <summary>
/// WebApi 层 — 组装所有依赖
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// Application 层
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommand).Assembly));
// Infrastructure 层
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, AppDbContext>();
// WebApi 层
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateOrderRequest request)
{
var command = new CreateOrderCommand(request.UserId, request.Items);
var orderId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = orderId }, new { Id = orderId });
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await _mediator.Send(new GetOrderQuery(id));
return result != null ? Ok(result) : NotFound();
}
}依赖规则
| 层 | 可以引用 | 不能引用 |
|---|---|---|
| Domain | 无 | Application、Infrastructure、WebApi |
| Application | Domain | Infrastructure、WebApi |
| Infrastructure | Domain、Application | WebApi |
| WebApi | Application、Infrastructure | Domain(通过 Application 间接访问) |
优点
缺点
总结
Clean Architecture 适合中大型项目,尤其是长期维护的企业应用。核心原则:依赖向内,业务逻辑不依赖基础设施。小项目直接用简单分层即可,不必强套 Clean Architecture。关键是在复杂度和可维护性之间找到平衡。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《整洁架构 Clean Architecture》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《整洁架构 Clean Architecture》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《整洁架构 Clean Architecture》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《整洁架构 Clean Architecture》最大的收益和代价分别是什么?
