mcpField
V
vertical-slice

vertical-slice

github.com/codewithmukesh开发7913.0k 次安装12 周前更新
代码仓库

关于 vertical-slice

面向 .NET 开发者的垂直切片架构技能,指导按功能组织代码并减少切片间耦合。涵盖处理器选择、模块边界和共享关注点的做法。

主要功能

  1. 01按功能组织代码
  2. 02单文件承载简单功能
  3. 03比较三种处理器模式
  4. 04按需引入模块边界
  5. 05集中管理共享关注点

适用场景

  1. 01设计 CRUD API 的功能结构
  2. 02为复杂应用划分模块
  3. 03选择 Mediator、Wolverine 或原生处理器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill vertical-slice

安装后在 Claude Code 中输入/vertical-slice确认已加载。

详细介绍

垂直切片架构(VSA)

核心原则

  1. 按功能组织,而非按层组织 — 每个功能都是一个独立的垂直切片,包含其端点、处理程序、请求/响应类型和验证。不必再在 Controllers/、Services/、Repositories/ 文件夹之间来回切换。
  2. 尽量减少功能之间的耦合 — 功能之间不应直接相互引用。共享的关注点放在 Common/ 或 Shared/ 目录中。
  3. 每个功能一个文件也可以 — 简单的 CRUD 端点不需要分散在各层的 5 个文件。从所有内容都放在一个文件中开始,只有在复杂度确有需要时才拆分。
  4. 处理程序是工作单元 — 每个处理程序只做一件事。不要创建有 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