Stay Hungry,Stay Foolish!

A Complete Guide to Clean Architecture

A Complete Guide to Clean Architecture

https://jdaniel1987.github.io/CleanArchitecture

 

image

 

 

 

这篇博文的核心在于通过分层来解耦,而 Program.cs 正是实现这种解耦的“总装车间”。将两者结合,能完美地展示从理论到实践的闭环。

 

https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles

image

 

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();

这段代码清晰地展示了分离:

  1. 构造阶段:在 builder 部分,我们告诉 DI 容器如何组装对象。例如,AddScoped<IOrderRepository, SqlOrderRepository>() 这条规则,就是连接抽象与实现的桥梁。
  2. 运行阶段app.Run() 之后,当请求到达 OrderController 时,框架会自动根据我们设定的规则,创建 PlaceOrderUseCase,并自动注入它所需的 IOrderRepository 实现。业务代码本身完全干净,不涉及任何 new 操作。

✨ 整洁架构的优势与挑战

优势

  • 可维护性:关注点分离,修改外部框架或数据库对核心业务影响极小。
  • 可测试性:业务逻辑不依赖外部资源,可以轻松编写单元测试。
  • 灵活性:可以轻松替换 UI 框架、数据库或任何外部服务。

挑战

  • 复杂性:对于小型项目,过多的抽象和分层可能显得繁琐。
  • 学习曲线:团队需要理解依赖倒置、依赖注入等概念才能有效实施。

📌 总结

整洁架构提供了一套强大的原则来构建可维护、可测试和灵活的系统。而“构造与运行分离”是实现这些原则的关键实践。通过在 Program.cs 中集中管理对象的创建,我们可以确保核心业务逻辑的纯净和独立,从而构建出真正健壮的软件。

 

posted @ 2026-08-25 12:17  lightsong  阅读(4)  评论(0)    收藏  举报
千山鸟飞绝,万径人踪灭