A Complete Guide to Clean Architecture
A Complete Guide to Clean Architecture
https://jdaniel1987.github.io/CleanArchitecture

这篇博文的核心在于通过分层来解耦,而 Program.cs 正是实现这种解耦的“总装车间”。将两者结合,能完美地展示从理论到实践的闭环。
https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles

https://github.com/fanqingsong/CleanArchitecture_extension
https://github.com/fanqingsong/CleanArchitecture/tree/main/MinimalClean
🏛️ 整洁架构实战:用“构造与运行分离”打造纯净核心
整洁架构(Clean Architecture)由罗伯特·C·马丁(Robert C. Martin,又称 Uncle Bob)推广,它强调通过清晰的分层和边界来分离软件系统,确保业务逻辑独立于 UI、数据库或框架等外部细节。这种分离使得系统更易于维护、测试和扩展。
本文将深入探讨整洁架构的核心概念,并展示如何通过“构造与运行分离”这一关键实践,在 .NET 项目中落地这些原则。
🧠 整洁架构的核心原则
整洁架构的魅力在于其清晰的规则,这些规则共同守护着核心业务逻辑的纯净。
1. 关注点分离 (Separation of Concerns)
整洁架构通过将代码组织成不同的层来实现清晰的职责划分。依赖关系应该单向流动:从外层(基础设施、UI)指向内层(业务逻辑)。
2. 依赖倒置原则 (Dependency Inversion Principle, DIP)
这是实现解耦的基石。它指出:高层模块不应该依赖低层模块,二者都应该依赖其抽象。
在整洁架构中,这意味着我们的业务逻辑(高层模块)只依赖接口,而具体的数据库或外部服务实现(低层模块)也去实现这些接口。
示例:依赖倒置
// 高层模块 - 业务逻辑
public class OrderService
{
private readonly IOrderRepository _orderRepository;
// 依赖通过构造函数注入,只依赖抽象 IOrderRepository
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public void PlaceOrder(Order order)
{
// 业务逻辑
_orderRepository.Save(order);
}
}
// 低层模块 - 具体实现
public class SqlOrderRepository : IOrderRepository
{
public void Save(Order order)
{
// 将订单保存到 SQL 数据库
}
}
3. 实体 (Entities)
实体是封装了企业级业务规则的核心业务对象。它们应该完全独立于外部服务、数据库和 UI。
public class Order
{
public Guid Id { get; set; }
public DateTime OrderDate { get; set; }
public Customer Customer { get; set; }
public List<OrderItem> Items { get; set; }
// 业务逻辑:计算订单总额
public decimal TotalAmount => Items.Sum(item => item.Price * item.Quantity);
}
4. 用例 (Use Cases / Application Layer)
用例层包含特定于应用程序的业务逻辑。它协调实体和外部服务(如仓储、通知)之间的交互,但同样是通过抽象接口进行的。
public class PlaceOrderUseCase
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
public PlaceOrderUseCase(IOrderRepository orderRepository, IPaymentService paymentService)
{
_orderRepository = orderRepository;
_paymentService = paymentService;
}
public void Execute(Order order)
{
// 协调业务逻辑
_paymentService.ProcessPayment(order);
_orderRepository.Save(order);
}
}
5. 接口适配器 (Interface Adapters)
这一层负责数据格式的转换。例如,将数据库的数据模型转换为用例层需要的实体,或将实体转换为 UI 层需要的 DTO。
public class OrderDto
{
public Guid Id { get; set; }
public DateTime OrderDate { get; set; }
public string CustomerName { get; set; }
}
// 在某个 Mapper 类中
public OrderDto ToOrderDto(Order order)
{
return new OrderDto()
{
Id = order.Id,
OrderDate = order.OrderDate,
CustomerName = order.Customer.Name
};
}
6. 框架与外部工具 (Frameworks and External Tools)
最外层由数据库、Web 框架等具体技术实现组成。它们不包含业务逻辑,只负责实现内层定义的接口。
🏗️ 整洁架构的分层结构
整洁架构通常被描绘成同心圆,依赖关系由外向内。
- 表现层 (Presentation):负责与用户交互,如 MVC 控制器、Razor 页面或 API 端点。
- 基础设施层 (Infrastructure):提供技术能力,如数据库访问(Entity Framework)、发送邮件、日志记录等。
- 应用层 (Application):包含用例和应用程序特定的逻辑,协调数据流。
- 领域层 (Domain):核心,包含实体、值对象和核心业务规则。
🔗 依赖方向 vs 数据流向
一个常见的误区是混淆依赖方向和数据流向。
- 依赖方向:代码的引用关系,始终由外层指向内层。
- 数据流向:请求和响应的流动路径,通常是从表现层进入,穿过各层到达领域层,再原路返回。
🚀 实践关键:构造与运行分离
理解了分层和依赖倒置,我们如何实现它?答案就是“构造与运行分离”。这个思想在 .NET 中通过依赖注入(DI)容器完美实现。
- 构造 (Construction):负责创建对象及其所有依赖。这发生在应用程序启动时,在一个集中的地方,称为“组合根”(Composition Root)。
- 运行 (Runtime):负责执行具体的业务逻辑。运行时的对象(如
OrderService)不关心依赖是如何创建的,只通过构造函数接收它们。
在 ASP.NET Core 中,Program.cs 文件就是我们的“组合根”。
完整的 Program.cs 示例
using Infrastructure; // 基础设施层
using Application; // 应用层
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// ==============================================================================
// 1. 构造阶段 (Construction Phase)
// ==============================================================================
// 这是“组合根”,所有依赖关系都在这里集中配置。
// 业务逻辑层(Application 和 Domain)完全不知道这里的存在。
// --- 1.1 添加框架服务 ---
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// --- 1.2 添加基础设施层服务 ---
// 配置数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 注册具体实现:当需要 IOrderRepository 时,提供 SqlOrderRepository 实例
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddScoped<IEmailSender, SmtpEmailSender>();
// --- 1.3 添加应用层服务 ---
// 注册用例或 MediatR
builder.Services.AddScoped<PlaceOrderUseCase>();
var app = builder.Build();
// ==============================================================================
// 2. 运行阶段 (Runtime Phase)
// ==============================================================================
// 配置 HTTP 请求处理管道。应用程序开始监听并响应请求。
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
这段代码清晰地展示了分离:
- 构造阶段:在
builder部分,我们告诉 DI 容器如何组装对象。例如,AddScoped<IOrderRepository, SqlOrderRepository>()这条规则,就是连接抽象与实现的桥梁。 - 运行阶段:
app.Run()之后,当请求到达OrderController时,框架会自动根据我们设定的规则,创建PlaceOrderUseCase,并自动注入它所需的IOrderRepository实现。业务代码本身完全干净,不涉及任何new操作。
✨ 整洁架构的优势与挑战
优势
- 可维护性:关注点分离,修改外部框架或数据库对核心业务影响极小。
- 可测试性:业务逻辑不依赖外部资源,可以轻松编写单元测试。
- 灵活性:可以轻松替换 UI 框架、数据库或任何外部服务。
挑战
- 复杂性:对于小型项目,过多的抽象和分层可能显得繁琐。
- 学习曲线:团队需要理解依赖倒置、依赖注入等概念才能有效实施。
📌 总结
整洁架构提供了一套强大的原则来构建可维护、可测试和灵活的系统。而“构造与运行分离”是实现这些原则的关键实践。通过在 Program.cs 中集中管理对象的创建,我们可以确保核心业务逻辑的纯净和独立,从而构建出真正健壮的软件。

浙公网安备 33010602011771号