关于 vertical-slice
面向 .NET 开发者的垂直切片架构技能,指导按功能组织代码并减少切片间耦合。涵盖处理器选择、模块边界和共享关注点的做法。
主要功能
- 01按功能组织代码
- 02单文件承载简单功能
- 03比较三种处理器模式
- 04按需引入模块边界
- 05集中管理共享关注点
适用场景
- 01设计 CRUD API 的功能结构
- 02为复杂应用划分模块
- 03选择 Mediator、Wolverine 或原生处理器
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ codewithmukesh/ dotnet-claude-kit --skill vertical-slice安装后在 Claude Code 中输入/vertical-slice确认已加载。
详细介绍
垂直切片架构(VSA)
核心原则
- 按功能组织,而非按层组织 — 每个功能都是一个独立的垂直切片,包含其端点、处理程序、请求/响应类型和验证。不必再在 Controllers/、Services/、Repositories/ 文件夹之间来回切换。
- 尽量减少功能之间的耦合 — 功能之间不应直接相互引用。共享的关注点放在
Common/或Shared/目录中。 - 每个功能一个文件也可以 — 简单的 CRUD 端点不需要分散在各层的 5 个文件。从所有内容都放在一个文件中开始,只有在复杂度确有需要时才拆分。
- 处理程序是工作单元 — 每个处理程序只做一件事。不要创建有 20 个方法的臃肿服务。
模式
功能文件夹结构
src/
MyApp.Api/
Features/
Orders/
CreateOrder.cs # Request, Handler, Response, Endpoint — all in one file
GetOrder.cs
ListOrders.cs
CancelOrder.cs
Shared/
OrderMapper.cs # Shared within the Orders feature only
Products/
CreateProduct.cs
GetProduct.cs
Common/
Behaviors/
ValidationBehavior.cs # Cross-cutting Mediator pipeline behavior
Persistence/
AppDbContext.cs
Extensions/
ServiceCollectionExtensions.cs
Program.cs
模式 A:Mediator 处理程序(推荐默认选项)
由源生成的 mediator,采用 MIT 许可证,不使用反射,兼容 Native AOT。使用 IRequest<T> / IRequestHandler<TRequest, TResponse> 和管道行为。API 与 MediatR 几乎相同,但速度更快且免费。程序包:Mediator.Abstractions + Mediator.SourceGenerator。
// Features/Orders/CreateOrder.cs
public static class CreateOrder
{
public record Command(string CustomerId, List<OrderItemDto> Items) : IRequest<Result<OrderResponse>>;
public record OrderItemDto(string ProductId, int Quantity);
public record OrderResponse(Guid Id, decimal Total, DateTime CreatedAt);
public class Validator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.Items).NotEmpty();
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.ProductId).NotEmpty();
item.RuleFor(x => x.Quantity).GreaterThan(0);
});
}
}
internal sealed class Handler(AppDbContext db, TimeProvider clock) : IRequestHandler<Command, Result<OrderResponse>>
{
public async ValueTask<Result<OrderResponse>> Handle(Command request, CancellationToken ct)
{
var order = Order.Create(request.CustomerId, request.Items, clock.GetUtcNow());
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return Result.Success(new OrderResponse(order.Id, order.Total, order.CreatedAt));
}
}
}
// Registration in Program.cs or module DI
builder.Services.AddMediator();
// Features/Orders/OrderEndpoints.cs — auto-discovered via IEndpointGroup
public sealed class OrderEndpoints : IEndpointGroup
{
public void Map(IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/orders").WithTags("Orders");
group.MapPost("/", async (CreateOrder.Command command, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(command, ct);
return result.IsSuccess
? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)
: result.ToProblemDetails();
})
.WithName("CreateOrder").Produces<CreateOrder.OrderResponse>(201)
.ProducesValidationProblem()
.AddEndpointFilter<ValidationFilter<CreateOrder.Command>>();
}
}
模式 B:Wolverine 处理程序
基于约定,无需实现接口。Wolverine 根据方法签名发现处理程序。
// Features/Orders/CreateOrder.cs
public static class CreateOrder
{
public record Command(string CustomerId, List<OrderItemDto> Items);
public record OrderItemDto(string ProductId, int Quantity);
public record OrderResponse(Guid Id, decimal Total, DateTime CreatedAt);
// Wolverine discovers this by convention (static Handle method)
public static async Task<Result<OrderResponse>> Handle(
Command command,
AppDbContext db,
TimeProvider clock,
CancellationToken ct)
{
var order = Order.Create(command.CustomerId, command.Items, clock.GetUtcNow());
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return Result.Success(new OrderResponse(order.Id, order.Total, order.CreatedAt));
}
}
模式 C:原始处理程序类(无库)
不依赖外部项的直接处理程序类。适用于小型项目,或希望完全掌控实现的团队。
// Features/Orders/CreateOrder.cs
public static class CreateOrder
{
public record Command(string CustomerId, List<OrderItemDto> Items);
public record OrderItemDto(string ProductId, int Quantity);
public record OrderResponse(Guid Id, decimal Total, DateTime CreatedAt);
internal class Handler(AppDbContext db, TimeProvider clock)
{
public async Task<Result<OrderResponse>> ExecuteAsync(Command command, CancellationToken ct)
{
var order = Order.Create(command.CustomerId, command.Items, clock.GetUtcNow());
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return Result.Success(new OrderResponse(order.Id, order.Total, order.CreatedAt));
}
}
}
// Endpoint wiring — Result maps to HTTP response
group.MapPost("/", async (CreateOrder.Command command, CreateOrder.Handler handler, CancellationToken ct) =>
{
var result = await handler.ExecuteAsync(command, ct);
return result.IsSuccess
? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)
: result.ToProblemDetails();
});
添加模块边界(可选)
对于发展到超出单个项目规模的大型应用,可引入模块边界。每个模块都是一个独立的类库,拥有自己的功能和 DbContext。
src/
MyApp.Api/ # Host — wires modules together
Program.cs
Modules/
ModuleExtensions.cs # app.MapOrderModule(), app.MapCatalogModule()
MyApp.Orders/ # Module — own features, own DbContext
Features/
CreateOrder.cs
Persistence/
OrdersDbContext.cs
OrdersModule.cs # IServiceCollection + IEndpointRouteBuilder extensions
MyApp.Catalog/ # Module
Features/
CreateProduct.cs
Persistence/
CatalogDbContext.cs
CatalogModule.cs
模块通过以下方式通信:
- 集成事件(首选)— 异步,通过 Wolverine 或 MassTransit 解耦
- 共享契约 — 使用包含 DTO/接口的
MyApp.Contracts项目(谨慎使用)
共享关注点
跨领域关注点放在功能文件夹之外:
// Common/Behaviors/ValidationBehavior.cs (Mediator pipeline)
public sealed class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IMessage
{
public async ValueTask<TResponse> Handle(
TRequest request,
MessageHandlerDelegate<TRequest, TResponse> next,
CancellationToken ct)
{
var context = new ValidationContext<TRequest>(request);
var failures = validators
.Select(v => v.Validate(context))
.SelectMany(r => r.Errors)
.Where(f => f is not null)
.ToList();
if (failures.Count > 0)
throw new ValidationException(failures);
return await next(request, ct);
}
}
反模式
不要在切片内部创建分层抽象
// BAD — a feature folder with its own service layer and repository
Features/
Orders/
CreateOrder.cs
IOrderService.cs # unnecessary abstraction
OrderService.cs # unnecessary abstraction
IOrderRepository.cs # unnecessary abstraction
OrderRepository.cs # unnecessary abstraction
// GOOD — handler talks directly to DbContext
Features/
Orders/
CreateOrder.cs # handler uses AppDbContext directly
不要直接交叉引用功能
// BAD — CreateOrder directly calls GetProduct handler
var product = await _getProductHandler.Handle(new GetProduct.Query(productId));
// GOOD — query the database directly or use a shared read model
var product = await db.Products.FindAsync(productId, ct);
不要把所有内容都放进一个臃肿的功能文件中
// BAD — 500-line file with CRUD + business logic + mapping
public static class Orders
{
// Create, Read, Update, Delete, Cancel, Refund, Export...
}
// GOOD — one file per operation
Features/Orders/CreateOrder.cs
Features/Orders/GetOrder.cs
Features/Orders/CancelOrder.cs
决策指南
| 场景 | 建议 |
|---|---|
| 新项目(默认) | 模式 A — Mediator(源生成、MIT 许可、速度快) |
| 需要在一个库中同时使用 mediator 和消息传递 | 模式 B — Wolverine(也能处理事件/队列) |
| 希望完全掌控实现,不依赖任何项 | 模式 C — 原始处理程序类 |
| 现有 MediatR 代码库且已获得许可 | 已获许可则继续使用 MediatR;否则迁移到 Mediator(API 几乎相同) |
| 单体应用日益复杂 | 添加模块边界,并在每个模块中保持 VSA |
| 简单的 CRUD 功能 | 单个文件:请求 + 处理程序 + 端点 |
| 复杂功能(saga、事件) | 在功能文件夹中放置多个文件,仍然就近组织 |
| 在功能之间共享逻辑 | 提取到 Common/,不要放到另一个功能中 |
评论
支持 Markdown