C# 垂直切片架构方法 C#如何实现Vertical Slice Architecture

3次阅读

Vertical Slice Architecture 是一种按功能组织代码的约定而非框架,每个切片(如 CreateOrder)包含完整功能代码并严格禁止跨切片引用,依赖通过 Shared 项目或显式注册隔离,核心在于团队对边界的纪律性遵守。

C# 垂直切片架构方法 C#如何实现Vertical Slice Architecture

Vertical Slice Architecture 在 C# 中不是框架而是组织约定

它不依赖特定库,而是通过项目结构、命名空间和依赖流向强制“按功能切片”,每个切片(如 CreateOrder)包含该功能所需的全部代码:API 入口、DTO、验证、业务逻辑、仓储调用,甚至测试。关键不是“怎么引入”,而是“怎么约束自己不跨切片引用”。

如何组织项目结构避免横向污染

常见错误是把 ControllersServicesModels 按层平铺,结果一个修改牵动整个解决方案。正确做法是按功能建文件夹,每个切片自成闭环:

  • 根目录下直接建 Features/Orders/CreateOrder/,里面放 CreateOrderCommand.csCreateOrderHandler.csCreateOrderEndpoint.csCreateOrderValidator.cs
  • 所有类型都放在 Features.Orders.CreateOrder 命名空间下,禁止跨切片引用(比如 Features.Customers.GetCustomerQuery 不得出现在 CreateOrderHandler 中)
  • 共享类型(如 Result<t></t>AppException)必须放在独立的 Shared 项目或命名空间中,且只允许被引用,不能反向依赖

MediatR 是最常用但非必需的实现载体

MediatR 能自然支撑请求/响应模型,让每个切片以 IRequest/IRequestHandler 形式封装,但它只是工具——你完全可以用原生 Func<trequest cancellationtoken task>></trequest> 或自定义接口替代。重点在于:每个切片对外暴露单一入口点,内部不暴露实现细节。

示例切片入口:

public record CreateOrderCommand(string CustomerId, List<OrderItemDto> Items) : IRequest<Result<Guid>>; public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Result<Guid>> {     private readonly IOrderRepository _repo;     public CreateOrderHandler(IOrderRepository repo) => _repo = repo; <pre class="brush:php;toolbar:false;">public async Task<Result<Guid>> Handle(CreateOrderCommand req, CancellationToken ct) {     // 业务逻辑内聚在此,不泄露给其他切片     var order = new Order(req.CustomerId);     foreach (var item in req.Items) order.AddItem(item.ProductId, item.Quantity);     await _repo.AddAsync(order, ct);     return Result.Success(order.Id); }

}

Startup 和依赖注入需配合切片边界

注册时不能全局扫 Assembly.GetExecutingAssembly(),否则会无意中拉入其他切片的 Handler。推荐方式:

  • 在每个切片目录里加 CreateOrderRegistration.cs,显式注册该切片所需服务:services.AddScoped<irequesthandler result>>, CreateOrderHandler>();</irequesthandler>
  • 或者用 MediatRAddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommand).Assembly)),但必须确保每个切片编译为独立程序集(即每个 Features.Xxx 是单独的 .csproj)
  • API 端点也应按切片注册:app.MapPost("/orders", CreateOrderEndpoint.Handle);,而不是集中写在 Program.cs 顶部

真正的难点不在代码怎么写,而在团队是否能守住“一个切片只做一件事、不越界复用、不为‘复用’牺牲边界”的纪律。一旦开始在 CreateOrderHandler 里直接 new GetCustomerQuery,垂直切片就退化成带文件夹的三层架构了。

text=ZqhQzanResources