Vertical Slice

codewithmukesh/dotnet-claude-kit/skills/vertical-slice

by codewithmukesh23300897f4d1No license754 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

Vertical Slice Architecture (VSA) for .NET applications — one of several supported architectures in dotnet-claude-kit. Covers feature folders, endpoint grouping, and handler patterns for Mediator, Wolverine, and raw handler classes. Load this skill when the architecture-advisor recommends VSA, when working in an existing VSA codebase, when adding features to a feature-folder project, or when discussing vertical slice patterns, feature folders, or handler patterns.

Instructions onlySoftware Development
AI-generated overview

Guides .NET developers in structuring applications with Vertical Slice Architecture, covering feature folders, endpoint grouping and handler patterns.

What it does
This skill documents Vertical Slice Architecture (VSA) for .NET applications, describing feature folders, endpoint grouping and handler patterns. It presents three handler approaches — a source-generated Mediator, Wolverine conventions, and raw handler classes — with code examples for each. It also covers optional module boundaries, shared cross-cutting concerns, anti-patterns and a decision guide for choosing a pattern.
When to use it
Use it when an architecture advisor recommends VSA, when working in an existing VSA codebase, or when adding features to a feature-folder project. It is also relevant when discussing vertical slice patterns, feature folders or handler patterns.
Requirements
No scripts are shipped; it is instructions only. The examples assume a .NET project and reference packages such as Mediator.Abstractions, Mediator.SourceGenerator, Wolverine, FluentValidation and Entity Framework Core, but nothing needs to be installed to read the guidance.

Vertical Slice Architecture (VSA)

Core Principles

  1. Organize by feature, not by layer — Each feature is a self-contained vertical slice containing its endpoint, handler, request/response types, and validation. No more jumping between Controllers/, Services/, Repositories/ folders.
  2. Minimize cross-feature coupling — Features should not reference each other directly. Shared concerns go in a Common/ or Shared/ directory.
  3. One file per feature is fine — A simple CRUD endpoint doesn't need 5 files spread across layers. Start with everything in one file, extract only when complexity demands it.
  4. The handler is the unit of work — Each handler does one thing. No god-services with 20 methods.

Patterns

Feature Folder Structure

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

Pattern A: Mediator Handlers (Recommended Default)

Source-generated mediator — MIT licensed, no reflection, Native AOT compatible. Uses IRequest<T> / IRequestHandler<TRequest, TResponse> with pipeline behaviors. Near-identical API to MediatR but faster and free. Package: Mediator.Abstractions + Mediator.SourceGenerator.

csharp
// 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 DIbuilder.Services.AddMediator();
// Features/Orders/OrderEndpoints.cs — auto-discovered via IEndpointGrouppublic 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>>();    }}

Pattern B: Wolverine Handlers

Convention-based — no interfaces to implement. Wolverine discovers handlers by method signature.

csharp
// 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));    }}

Pattern C: Raw Handler Classes (No Library)

Direct handler classes with no external dependency. Good for small projects or teams that want full control.

csharp
// 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 responsegroup.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();});

Adding Module Boundaries (Optional)

For larger applications that grow beyond a single project, introduce module boundaries. Each module is a separate class library with its own features and 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

Modules communicate via:

  • Integration events (preferred) — async, decoupled via Wolverine or MassTransit
  • Shared contracts — a MyApp.Contracts project with DTOs/interfaces (use sparingly)

Shared Concerns

Cross-cutting concerns live outside feature folders:

csharp
// 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);    }}

Anti-patterns

Don't Create Layered Abstractions Within a Slice

csharp
// BAD — a feature folder with its own service layer and repositoryFeatures/  Orders/    CreateOrder.cs    IOrderService.cs         # unnecessary abstraction    OrderService.cs          # unnecessary abstraction    IOrderRepository.cs      # unnecessary abstraction    OrderRepository.cs       # unnecessary abstraction
// GOOD — handler talks directly to DbContextFeatures/  Orders/    CreateOrder.cs           # handler uses AppDbContext directly

Don't Cross-reference Features Directly

csharp
// BAD — CreateOrder directly calls GetProduct handlervar product = await _getProductHandler.Handle(new GetProduct.Query(productId));
// GOOD — query the database directly or use a shared read modelvar product = await db.Products.FindAsync(productId, ct);

Don't Put Everything in One God Feature File

csharp
// BAD — 500-line file with CRUD + business logic + mappingpublic static class Orders{    // Create, Read, Update, Delete, Cancel, Refund, Export...}
// GOOD — one file per operationFeatures/Orders/CreateOrder.csFeatures/Orders/GetOrder.csFeatures/Orders/CancelOrder.cs

Decision Guide

ScenarioRecommendation
New project (default)Pattern A — Mediator (source-generated, MIT, fast)
Need mediator + messaging in one libPattern B — Wolverine (also handles events/queues)
Want full control, no dependenciesPattern C — Raw handler classes
Existing MediatR codebase with licenseKeep MediatR if licensed; otherwise migrate to Mediator (near-identical API)
Monolith growing complexAdd module boundaries, keep VSA within each module
Simple CRUD featureSingle file: request + handler + endpoint
Complex feature (saga, events)Multiple files in feature folder, still colocated
Sharing logic between featuresExtract to Common/ — not to another feature

Source and attribution

Source:codewithmukesh/dotnet-claude-kitinskills/vertical-sliceat commit2330089

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from codewithmukesh/dotnet-claude-kit

Wrap Up

codewithmukesh

Captures end-of-session work, pending tasks and learnings into a handoff file, and reloads it at session start.

Productivity & Workflow754updated 2 months ago

Workflow Mastery

codewithmukesh

Claude Code workflow mastery for .NET developers. Covers parallel execution with git worktrees, plan mode strategy, verification loops, auto-formatting hooks, permission setup for dotnet CLI, prompting techniques, subagent patterns, and context discipline — token budget management, MCP-first navigation, lazy loading, and subagent isolation — all adapted for the .NET ecosystem. Load this skill when setting up Claude Code for a .NET project, optimizing workflows, running parallel sessions, when context is running low or sessions feel sluggish, when exploring a large codebase efficiently, or when the user mentions "productivity", "workflow", "parallel", "worktree", "plan mode", "permissions", "hooks", "10x", "setup Claude Code", "speed up development", "context", "tokens", "budget", "running out of context", "too many files", or "large codebase". Inspired by tips from Boris Cherny (creator of Claude Code) and the Anthropic team.

Awaiting classification754updated 2 months ago

Testing

codewithmukesh

Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".

Awaiting classification754updated 2 months ago

Tdd

codewithmukesh

Guided test-driven development workflow for .NET 10 using xUnit v3, WebApplicationFactory, Testcontainers, and Verify snapshots. Follows the strict red-green-refactor cycle. Use when: "TDD", "test-driven", "let's TDD this", "red green refactor", "write the test first", or when building a feature with clear acceptance criteria.

Awaiting classification754updated 2 months ago

Spec

codewithmukesh

Turns a vague feature idea into an agreed, persisted specification file through structured questioning rounds.

Productivity & Workflow754updated 2 months ago

Serilog

codewithmukesh

Structured logging with Serilog for .NET 10 applications. Covers two-stage bootstrap, appsettings configuration, enrichers, sinks, request logging, destructuring, and Serilog.Expressions. Load this skill when setting up Serilog, configuring log sinks, enrichers, or structured logging, or when the user mentions "Serilog", "structured logging", "log enrichment", "Seq", "LogContext", "UseSerilog", "WriteTo", "message template", "Serilog.Expressions", "request logging", "log sink", "rolling file", or "audit log".

Awaiting classification754updated 2 months ago