其他
在 .NET 中构建中高领域复杂度…
适用于 .NET 应用需要清晰分层、用例驱动、基础设施可插拔且业务规则与框架解耦的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
在 .NET 中构建中高领域复杂度、需要特定
解决思路
- 依赖倒置:所有依赖指向内层;Domain 零项目引用,Application 只引用 Domain,Infrastructure 引用 Application 和 Domain,Api 引用所有层但依赖抽象,并通过项目引用让编译器强制约束。
- Domain 拥有规则:业务逻辑放在 Domain 层,以实体方法、领域服务或规格实现;Domain 不引用数据库、HTTP 或任何框架,只使用纯 C# 和 .NET primitives。
- 用例是工作单元:每个 command 或 query 在 Application 层对应单个类;用例负责编排领域对象、通过抽象持久化并返回结果,避免包含 20 个方法的 service 类。
- Infrastructure 是插件:EF Core、外部 API、邮件发送器、文件存储等放在 Infrastructure,并实现 Application 或 Domain 定义的接口;替换实现时不修改业务逻辑。
- API 层保持薄:Endpoints 只把 HTTP 映射到用例、把用例结果映射为 HTTP 响应;endpoint 中不放业务逻辑。
- 项目布局:创建 src/MyApp.Domain/,包含 Entities/Order.cs、Entities/OrderItem.cs、Enums/OrderStatus.cs、Exceptions/DomainException.cs、Interfaces/IOrderRepository.cs(仅当查询需要超出 DbSet)、Common/Entity.cs(带 Id 的基实体)、Common/Result.cs(Result 模式类型)。
- 项目布局:创建 src/MyApp.Application/,包含 Common/Behaviors/ValidationBehavior.cs(Mediator pipeline behavior)、Common/Interfaces/IAppDbContext.cs(优先于 repository 的 DbContext 抽象),以及 Orders/Commands/CreateOrder/ 下的 CreateOrderCommand.cs、CreateOrderHandler.cs、CreateOrderValidator.cs,和 Orders/Queries/GetOrder/ 下的 GetOrderQuery.cs、GetOrderHandler.cs、OrderDto.cs。
- 项目布局:创建 src/MyApp.Infrastructure/,包含 Persistence/AppDbContext.cs(实现 IAppDbContext)、Persistence/Configurations/OrderConfiguration.cs、Persistence/Migrations/、Services/EmailSender.cs(实现 Application 中的 IEmailSender)、DependencyInjection.cs(AddInfrastructure 扩展)。
- 项目布局:创建 src/MyApp.Api/,包含 Endpoints/OrderEndpoints.cs(薄,映射 HTTP ↔ use cases)和 Program.cs。
- DbContext 抽象:在 Application/Common/Interfaces/IAppDbContext.cs 定义最小接口,包含 DbSet<Order> Orders、DbSet<Product> Products、Task<int> SaveChangesAsync(CancellationToken ct = default)。
- 实现 IAppDbContext:在 Infrastructure/Persistence/AppDbContext.cs 中让 AppDbContext 继承 DbContext 并实现 IAppDbContext;DbSet 属性用 Set<Order>() / Set<Product>() 暴露;在 OnModelCreating 中调用 modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly)。
- 优先 IAppDbContext 而非 IRepository:EF Core 的 DbSet 已经是 repository,绝大多数情况下不要再包一层抽象;只有复杂可复用查询才考虑 repository。
- Command 定义:在 Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs 用 record 定义 CreateOrderCommand(string CustomerId, List<OrderItemDto> Items) : IRequest<Result<Guid>>,并定义 OrderItemDto(string ProductId, int Quantity, decimal UnitPrice)。
- Command Handler:在 CreateOrderHandler.cs 中实现 internal sealed class CreateOrderHandler(IAppDbContext db, TimeProvider clock) : IRequestHandler<CreateOrderCommand, Result<Guid>>;Handle 中调用 Order.Create(...),执行 db.Orders.Add(order),await db.SaveChangesAsync(ct),返回 Result.Success(order.Id);使用 Mediator(source-generated, MIT)。
- Command Validator:在 CreateOrderValidator.cs 中实现 CreateOrderValidator : AbstractValidator<CreateOrderCommand>;规则包括 CustomerId NotEmpty、Items NotEmpty、对每个 item 校验 ProductId NotEmpty、Quantity GreaterThan(0)、UnitPrice GreaterThan(0)。
- Query 定义:在 Application/Orders/Queries/GetOrder/GetOrderQuery.cs 定义 GetOrderQuery(Guid OrderId) : IRequest<Result<OrderDto>> 和 OrderDto(Guid Id, string CustomerId, decimal Total, string Status, DateTimeOffset CreatedAt)。
- Query Handler:在 GetOrderHandler.cs 中实现 GetOrderHandler(IAppDbContext db) : IRequestHandler<GetOrderQuery, Result<OrderDto>>;用 db.Orders.Where(o => o.Id == request.OrderId).Select(o => new OrderDto(...)).FirstOrDefaultAsync(ct);找到返回 Result.Success(order),否则返回 Result.Failure<OrderDto>("Order not found")。
- Domain 实体结构:在 Domain/Entities/Order.cs 定义 Order : Entity;用 private readonly List<OrderItem> _items = [];,private Order() {} 供 EF Core;属性用 private set;Items 暴露为 IReadOnlyList<OrderItem> => _items.AsReadOnly()。
- Domain 工厂方法:实现 static Order Create(string customerId, IEnumerable<OrderItem> items, DateTimeOffset now),设置 Id = Guid.CreateVersion7()、CustomerId、Status = OrderStatus.Pending、CreatedAt = now,并逐个 AddItem。
- Domain 行为 AddItem:实现 AddItem(OrderItem item),将 item 加入 _items,并重算 Total = _items.Sum(i => i.Quantity * i.UnitPrice)。
- Domain 行为 Cancel:实现 Result Cancel();如果 Status 不是 OrderStatus.Pending,返回 Result.Failure("Only pending orders can be cancelled");否则设置 Status = OrderStatus.Cancelled 并返回 Result.Success()。
- Thin Endpoint 自动发现:每个 endpoint group 实现 IEndpointGroup,并通过 app.MapEndpoints() 自动发现;新增 endpoints 时 Program.cs 不变(完整 IEndpointGroup 和 EndpointExtensions 见 minimal-api 技能)。
- OrderEndpoints 映射:OrderEndpoints : IEndpointGroup 的 Map 中创建 group = app.MapGroup("/api/orders").WithTags("Orders");注册 group.MapPost("/", CreateOrder).WithName("CreateOrder")、group.MapGet("/{id:guid}", GetOrder).WithName("GetOrder")、group.MapGet("/", ListOrders).WithName("ListOrders")。
- CreateOrder endpoint:静态 async Task<IResult> CreateOrder(CreateOrderCommand command, ISender sender, CancellationToken ct);sender.Send(command, ct) 后,成功返回 TypedResults.Created 指向 /api/orders/{result.Value} 并带 result.Value,失败返回 result.ToProblemDetails()。
- GetOrder endpoint:静态 async Task<IResult> GetOrder(Guid id, ISender sender, CancellationToken ct);sender.Send(new GetOrderQuery(id), ct) 后,成功返回 TypedResults.Ok(result.Value),失败返回 TypedResults.NotFound()。
- ListOrders endpoint:静态 async Task<IResult> ListOrders([AsParameters] ListOrdersQuery query, ISender sender, CancellationToken ct);sender.Send(query, ct) 后返回 TypedResults.Ok(result)。
- Infrastructure DI:在 Infrastructure/DependencyInjection.cs 提供 AddInfrastructure(this IServiceCollection services, IConfiguration config);调用 services.AddDbContext<AppDbContext>(options => options.UseNpgsql(config.GetConnectionString("DefaultConnection")));调用 services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());返回 services。
- 避免贫血领域模型:不要让实体只是数据袋、把 Total 和 Status 在 handler 中直接赋值;改为实体封装规则,例如用 Order.Create(customerId, items, clock.GetUtcNow()) 创建订单。
- 避免 Domain 引用 DbContext:不要在 Domain/Services/OrderService.cs 中依赖 AppDbContext;让 Domain 定义接口、Infrastructure 实现;优先 Application/Common/Interfaces/IAppDbContext.cs,仅在需要超出 DbSet 的查询抽象时用 Domain/Interfaces/IOrderRepository.cs。
- 避免 Fat Endpoints:不要在 endpoint 里 new Order、遍历 items、计算 Total、调用 db.Orders.Add 和 SaveChangesAsync;改为 endpoint 只接收 CreateOrderCommand 并委托 ISender.Send 给用例。
- 避免为每个实体建 Repository:不要为 IOrderRepository、IProductRepository、ICustomerRepository 重复 DbSet 功能;直接使用 IAppDbContext 和 DbSet<T>,只有复杂查询逻辑需要隔离测试或跨多个用例复用时才建 repository 接口。
- 解耦副作用:当通知、审计等副作用需要与主流程解耦时,添加 Domain events,并在主流程外处理这些事件。
适用边界 · 注意事项
- 领域复杂度低、系统生命周期短、团队不熟悉分层架构时,不要选用 Clean Architecture,简单 CRUD/VSA 更合适。
- 业务规则不涉及跨实体组不变量时,不必强行增加 Domain 层。
- 只需要简单 handler 且不需要 validation/logging 等 pipeline behaviors 时,不必引入 Mediator 或完整 CA 分层;当 handlers 开始需要不属于 Common/ 的共享领域逻辑时,再从 VSA 演进到 CA。