Skip to main content
SEMastery
Architectureintermediate

Vertical Slice Architecture in .NET: The Easy Guide

Learn Vertical Slice Architecture in .NET in simple words. Organise code by feature instead of by layer, with diagrams, real examples, a comparison with Clean Architecture, and when to use each.

11 min readUpdated November 27, 2025

A cake cut two different ways

Imagine a cake with several layers — sponge, cream, jam, and icing.

There are two ways to cut it. You could cut it horizontally, separating all the sponge from all the cream from all the jam. Now each plate has only one layer. To enjoy a proper bite, you would have to gather a piece from every plate and stack them yourself.

Or you could cut it vertically, like a normal slice. Each slice already has all the layers together — sponge, cream, jam, and icing in one piece. One slice is a complete, ready-to-eat bite.

This is exactly the difference between two ways of organising code. The horizontal cut is layered architecture (including Clean Architecture): all the controllers in one place, all the services in another, all the data access in a third. The vertical cut is Vertical Slice Architecture (VSA): everything a single feature needs lives together in one slice.

Let us see why slicing vertically often makes life easier, and when it does not.

The problem with horizontal layers

In a layered app, the code for one feature is scattered across many folders. To add a simple "Create Order" feature, you typically touch:

  • a Controller (in the API layer)
  • a Service (in the Application layer)
  • a Repository (in the Infrastructure layer)
  • maybe a DTO, a validator, and a mapping profile in yet more folders
Figure 1: In layered architecture, one feature is spread across many layers. You jump between folders to build or change it.

Nothing here is wrong on its own. But notice the pain: to understand or change one feature, you open five folders, and each of those folders is full of unrelated code from other features. The shared service and repository keep growing as every feature piles into them. Over time these shared layers become crowded and risky to touch — a change for one feature can accidentally break another.

The vertical slice idea

VSA cuts the other way. Instead of grouping by what kind of code it is (controller, service, repository), you group by which feature it belongs to. Each feature becomes a self-contained slice holding everything it needs.

Figure 2: In Vertical Slice Architecture, each feature owns all its code in one place. Features sit side by side, independent of each other.

Now, to work on "Create Order," you open one folder. Everything is right there. You build, test, and ship the feature without wading through shared layers. As Jimmy Bogard — who popularised VSA in the .NET world — puts it, you minimise the number of things you must touch to make a change, and you keep things that change together close together.

Layered vs Vertical Slice

Layered: cut by type
Spread across folders
VSA: cut by feature
All in one slice

Steps

1

Layered

Controllers, services, repositories in separate layers

2

Scattered

One feature lives in many folders at once

3

Sliced

Group everything for a feature together

4

Whole

Change a feature by touching one place

Layered architecture cuts by technical type. Vertical slice cuts by feature, keeping each one whole.

What a slice looks like in .NET

A slice is usually a single folder per feature. Inside it sits the request, the response, the handler with the logic, and the endpoint. Here is a clean folder layout:

Features/
├── Orders/
│   ├── CreateOrder/
│   │   ├── CreateOrderEndpoint.cs
│   │   ├── CreateOrderHandler.cs
│   │   └── CreateOrderRequest.cs
│   ├── GetOrder/
│   │   ├── GetOrderEndpoint.cs
│   │   └── GetOrderHandler.cs
│   └── CancelOrder/
│       ├── CancelOrderEndpoint.cs
│       └── CancelOrderHandler.cs

And a single slice in code might look like this — endpoint and handler living together, using a minimal API so no extra library is needed:

// CreateOrder/CreateOrderRequest.cs
public record CreateOrderRequest(Guid CustomerId, List<OrderItem> Items);
 
// CreateOrder/CreateOrderHandler.cs
public class CreateOrderHandler(AppDbContext db)
{
    public async Task<Guid> Handle(CreateOrderRequest request, CancellationToken ct)
    {
        var order = Order.Create(request.CustomerId, request.Items);
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
        return order.Id;
    }
}
 
// CreateOrder/CreateOrderEndpoint.cs
public static class CreateOrderEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/orders", async (
            CreateOrderRequest request,
            CreateOrderHandler handler,
            CancellationToken ct) =>
        {
            var id = await handler.Handle(request, ct);
            return Results.Created($"/orders/{id}", new { id });
        });
}

Everything about creating an order is in one folder. A new developer can read this slice top to bottom and understand the whole feature without opening anything else.

Figure 3: A request flows straight through a single slice — endpoint to handler to database — and back.
💡

MediatR is the library that made vertical slices famous, turning each feature into a command/query plus a handler. It is now commercially licensed, so for new projects you can use plain handler classes (as above), minimal APIs, or free alternatives. The architecture does not depend on any single tool.

Vertical Slice vs Clean Architecture

These are often presented as rivals, but they answer different questions. Clean Architecture is about dependency direction (inner business rules must not depend on outer details). Vertical Slice is about code organisation (group by feature). Here is the comparison:

Clean ArchitectureVertical Slice
Cuts code byTechnical layer (Domain, App, Infra, API)Feature (CreateOrder, GetOrder…)
To change a featureTouch many layersTouch one slice
Shared codeLots, in shared layersLittle; some duplication is allowed
Best atEnforcing strict dependency rulesFast feature work, easy navigation
RiskCeremony and folder-hoppingSlices can drift in style without guidelines

Notice the row about duplication. VSA happily allows a little duplication between slices to avoid the "wrong abstraction." Two features that look similar today might need to change differently tomorrow; keeping them separate makes that easy. Clean Architecture instead pushes shared logic into common layers.

ℹ️

You do not have to choose only one. A common, powerful setup is to use Clean Architecture's project layers for dependency rules, and organise the application layer by feature slices inside it. You get strict dependencies and feature-first navigation.

What goes inside one slice

It helps to see the parts of a single slice laid out. A well-formed slice usually has four small pieces that work together:

Anatomy of a Vertical Slice

Request
Endpoint
Handler
Response

Steps

1

Request

The input data for this feature (a record/DTO)

2

Endpoint

Maps the HTTP route to the handler

3

Handler

The actual business logic and data access

4

Response

What the feature returns to the caller

A slice is small and complete: a request shape, an endpoint, a handler with the logic, and a response — all in one folder.

Handling cross-cutting concerns

A fair question: if each slice is independent, how do you handle things every feature needs — like validation, logging, and error handling — without copying them into every slice?

The answer is a small shared pipeline or wrapper that runs around your handlers. You write the concern once and apply it to all slices, while the slice itself stays focused on its own job. For example, validation can live in a tiny reusable step:

// A reusable validation step, shared by every slice that needs it.
public static class ValidationBehavior
{
    public static async Task<IResult> Validate<T>(
        T request, IValidator<T> validator, Func<Task<IResult>> next)
    {
        var result = await validator.ValidateAsync(request);
        if (!result.IsValid)
            return Results.ValidationProblem(result.ToDictionary());
 
        return await next(); // valid → run the slice's handler
    }
}

Each slice still owns its own logic, but shared concerns like validation, logging, and authorization are written once and reused. This keeps slices small without duplicating the boring plumbing. The key is balance: share the generic plumbing, but keep the feature-specific logic inside the slice.

💡

A simple guideline: if a piece of code is about how the app works in general (logging, validation, error formatting), share it in a pipeline. If it is about what this feature does (how an order is created), keep it inside the slice. This line keeps slices clean without over-sharing.

A common misunderstanding

Just putting controllers and views in feature folders does not make your backend vertically sliced. And using a request/handler library does not automatically slice your code either. If your handlers still call into big shared services and repositories that every feature depends on, you have layered code wearing a slice costume.

True vertical slicing means each slice can do its own work — including its own data access — without being forced through shared layers. A little independence per slice is the whole point.

Another sign of healthy slices: you can read one feature from top to bottom without opening any other file. If understanding "Create Order" forces you to jump into a giant shared service that also serves twenty other features, the slice is leaking. Keep the path from request to response short and contained, and your slices stay easy to read, test, and change on their own.

When to use Vertical Slice Architecture

VSA is a great fit when:

  • Your app has many distinct features (most real business apps do).
  • You want teams to work in parallel without colliding in shared layers.
  • You value fast onboarding — new developers learn one slice at a time.
  • You are building a modular monolith, where slices group naturally into modules.

It is less necessary for:

  • Very small apps with only a handful of endpoints, where simple layering is already easy to navigate.
  • Pure library or domain code with no features to slice in the first place.
Choose…When…
Vertical SliceFeature-rich apps, parallel teams, fast iteration
Plain layeringTiny apps, prototypes
Clean + SlicesYou need strict dependency rules and feature-first code

Quick recap

  • Vertical Slice Architecture organises code by feature, not by technical layer — like cutting a cake into whole slices instead of separating the layers.
  • Each slice keeps its endpoint, logic, and data access together, so you change a feature by touching one place.
  • It avoids the crowded, risky shared layers of pure layered architecture, and even allows a little duplication to dodge the wrong abstraction.
  • It pairs beautifully with the modular monolith, and can be combined with Clean Architecture for strict dependencies plus feature-first code.
  • MediatR popularised it but is now commercial — the pattern works fine with plain handlers or minimal APIs.

Cut your code into whole slices, keep each feature self-contained, and your codebase becomes a tray of ready-to-eat pieces instead of a stack of separated layers you must assemble every time. The payoff shows up every day: features are faster to build, easier to read, simpler to test, and safer to change — because each one lives in a single, clear place that you can understand on its own.

References and further reading

Related Posts