Vertical Slice Architecture: The Best Ways to Structure Your Project
A simple, friendly guide to the best ways to structure a .NET 10 project with vertical slice architecture — folders, diagrams, tables, and real code.
A tiffin box, not a buffet table
Picture two ways to pack lunch for a school trip.
The buffet way keeps all the rice in one giant pot, all the dal in another pot, all the sabzi in a third pot, and all the rotis in a big stack. To serve one child, a teacher must run to four different pots and assemble the plate. If the dal pot tips over, every single plate is affected. Everything is shared, so everything is tangled.
The tiffin box way gives each child one small box. Inside that one box is their rice, their dal, their sabzi, and their roti — all together, all in one place. A child opens one box and the whole meal is right there. If one box spills, only that one child is affected. The other boxes are perfectly fine.
Vertical slice architecture packs your code like tiffin boxes. Each feature gets its own box. Inside that box lives everything the feature needs to do its job: the request that comes in, the logic that runs, the rules that check it, and the database call that saves it. To work on a feature, you open one box. Nothing else moves.
This guide walks through the best ways to structure that tiffin-box project in a real .NET 10 app. We will look at folders, files, naming, and the small choices that make a slice easy to live with.
Two ways to slice the same cake
Before any code, imagine a tall layered cake.
The old way cuts the cake flat. You get a plate of just sponge, a plate of just cream, a plate of just jam. This is the classic layered (horizontal) style: all controllers in one folder, all services in another, all repositories in a third. To add one feature you must run across every layer and touch a little of each.
The slice way cuts straight down. Each slice has a bit of sponge, cream, and jam together. This is vertical slice architecture: each feature keeps its request, its logic, its rules, and its data access in one place. To add a feature you add one slice. To delete it you delete one folder.
Notice the difference. In the layered picture, one feature lives spread across many boxes. In the slice picture, one feature lives inside one box. That single idea is the heart of everything below.
What lives inside one slice
A slice is small but complete. For a typical write feature, one slice holds four things.
| Part | Job | Everyday meaning |
|---|---|---|
| Request | The data that comes in | The order ticket a customer hands over |
| Validation | The rules that check the data | The waiter checking the ticket makes sense |
| Handler | The logic that does the work | The cook who prepares the dish |
| Endpoint | The door the request enters by | The counter where you place your order |
Keep these four together and you have a slice. When you read the slice, you see the whole story of one feature from top to bottom. You never have to jump to a far-away folder to understand what happens.
Here is the shape of a single slice that creates an order. This example keeps everything in one file, which is the simplest place to start.
// Features/Orders/CreateOrder.cs
public static class CreateOrder
{
public record Command(string ProductId, int Quantity) : IRequest<Guid>;
public class Validator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(c => c.ProductId).NotEmpty();
RuleFor(c => c.Quantity).GreaterThan(0);
}
}
public class Handler(AppDbContext db)
{
public async Task<Guid> Handle(Command command, CancellationToken ct)
{
var order = new Order
{
Id = Guid.NewGuid(),
ProductId = command.ProductId,
Quantity = command.Quantity,
};
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return order.Id;
}
}
}Everything for "create an order" sits in one place. The request, the rules, and the work are all visible at a glance. This is the tiffin box, in code.
The best ways to structure the project
There is no single correct folder tree, but a few patterns work well in practice. Let us look at the three most common, from simplest to most organised.
Way 1: One folder per feature group
Group slices by the area of the app they belong to. Orders go in an Orders folder, payments go in a Payments folder, and so on. Inside each area, every slice is one file.
Folder per feature group
Steps
src
project root
Features
all features live here
Orders
one business area
CreateOrder.cs
one full slice
This is the friendliest starting point. A newcomer opens the Orders folder and sees every order feature listed by name. The structure reads like a table of contents for the business.
Way 2: One folder per slice
When a feature grows, give it its own folder and split it into separate files. The request lives in one file, the handler in another, the validation in a third.
| File | Holds |
|---|---|
CreateOrderCommand.cs | the request shape |
CreateOrderHandler.cs | the logic |
CreateOrderValidator.cs | the rules |
CreateOrderEndpoint.cs | the route |
This keeps each file short and focused. The cost is more files to open. Use this only when a single-file slice starts to feel too tall to scroll comfortably.
Way 3: Shared kernel plus slices
Most apps need a little common code: the database context, base classes, and small helpers. Put those in a shared place, and keep every feature as a slice that leans on the shared parts only when it must.
The golden rule for the shared kernel is patience. Do not move code into it the first time you see a repeat. Wait until the same logic shows up in about three slices. A little copied code is cheaper to fix later than a wrong shared abstraction that every feature now depends on.
How a request travels through a slice
It helps to picture one request moving through the system from start to finish. The path is short and straight, because everything lives in one place.
There is no detour into a far folder. The endpoint receives the request, the validator checks it, the handler does the work, the database stores it, and the user gets an answer. If you ever need to debug POST /orders, you open one slice and read it top to bottom.
Wiring a slice into the app
A slice needs a door. In .NET 10, the cleanest door is a minimal API endpoint. Keep the endpoint inside the slice so the whole feature stays together.
// Features/Orders/CreateOrder.cs (continued)
public static class CreateOrderEndpoint
{
public static void MapCreateOrder(this IEndpointRouteBuilder app)
{
app.MapPost("/orders", async (
CreateOrder.Command command,
CreateOrder.Handler handler,
CancellationToken ct) =>
{
var id = await handler.Handle(command, ct);
return Results.Created($"/orders/{id}", new { id });
});
}
}Then register every slice's endpoint in one tidy place. Because each slice exposes a small Map method, the startup file stays short and readable.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<CreateOrder.Handler>();
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
var app = builder.Build();
app.MapCreateOrder();
// app.MapGetOrder();
// app.MapCancelOrder();
app.Run();Notice the route uses $"/orders/{id}" inside the code block, which is the safe place for curly braces. In prose you would write that route as GET /orders/{id} with backticks around it.
Do you need MediatR?
You may have seen vertical slice examples that use MediatR to send a command to a handler. That still works, but there is news to know.
On 2 July 2025, MediatR went commercial. Newer versions now need a paid licence for many professional teams. MassTransit announced the same move, with its version 9 turning commercial too. None of this breaks the vertical slice pattern, because the pattern never depended on those tools. It only depends on grouping code by feature.
You have three friendly choices in 2026.
| Choice | What it is | Cost |
|---|---|---|
| Call handlers directly | Inject the handler and call Handle yourself | Free, no library |
| Tiny dispatcher | Write a small class that finds and runs handlers | Free, a few lines |
| Wolverine | A fast, MIT-licensed mediator and messaging library | Free and open source |
The direct-call example shown earlier needs no library at all. The handler is just a class you inject and call. For most slices, that is enough, and it keeps your project light.
CQRS: the natural partner
Vertical slices pair beautifully with CQRS, which simply means you split reads from writes.
- A command changes data. Create an order, cancel an order, update an address.
- A query reads data. Get an order, list orders, search orders.
Each slice is naturally one or the other. A command slice does work and saves. A query slice just reads and returns. This split keeps each slice tiny, because a query never carries the heavy validation a command needs, and a command never carries the shaping a list view needs.
Commands and queries as slices
Steps
Request
comes in
Command?
decide write or read
Write to DB
command slice
Read from DB
query slice
You do not need a fancy framework for CQRS. The split is mostly a way of thinking. Name your slices CreateOrder, GetOrder, ListOrders, and the intent is already clear.
Common mistakes to avoid
Even a simple pattern can go wrong. Here are the traps to watch for.
Sharing too early. The biggest mistake is pulling code into a shared folder the moment two slices look alike. This couples your features together and kills the main benefit. Wait for the rule of three.
A giant shared service. Some teams keep one big OrderService that every slice calls. Now the slices are not independent; they all lean on one fat object. Keep the logic inside the slice instead.
Folders by file type. Do not create top-level folders called Handlers, Validators, and Endpoints with every feature's pieces scattered across them. That is the layered style wearing a slice costume. Group by feature first, by file type never.
Endpoints far from handlers. If the route lives in one project and the handler in another, you lost the tiffin box. Keep the door next to the kitchen.
When vertical slices shine, and when they do not
Vertical slices are a strong default, but they are not magic for every situation.
They shine when your app is a collection of features that each do one clear job: an orders API, a booking system, an admin panel. The more your work looks like "add a feature, change a feature, remove a feature," the better slices fit.
They struggle when a feature has deep, shared business rules that many slices must obey in exactly the same way. In that case you may add a small domain layer inside the slice, or borrow a few ideas from clean architecture for just that one complex feature. Mixing is allowed. Many teams in 2026 use slices on the outside and clean rules inside only where a feature truly earns the extra ceremony.
The honest summary is that vertical slices reduce the cost of change for most everyday features. You keep that benefit as long as each slice stays mostly self-contained.
Quick recap
- Vertical slice architecture groups all the code for one feature together, like packing one tiffin box per child instead of running between shared pots.
- The best starting structure is one folder per feature group, with each slice as a single file. Split a slice into many files only when it grows tall.
- A slice usually holds four parts: request, validation, handler, endpoint. Keep them together so one feature reads top to bottom.
- Use a small shared kernel for truly common code like the database context, and follow the rule of three before sharing anything.
- You do not need MediatR. It went commercial on 2 July 2025, so call handlers directly, write a tiny dispatcher, or use the free Wolverine library.
- Slices pair naturally with CQRS: each slice is either a command (write) or a query (read).
- Avoid sharing too early, giant shared services, and folders organised by file type.
References and further reading
- .NET application architecture guidance — Microsoft Learn
- Vertical Slice Architecture in .NET — Milan Jovanović
- MediatR and MassTransit Going Commercial: What This Means For You — Milan Jovanović
- Vertical Slice Architecture: The Best Ways to Structure Your Project — Anton DevTips
- VerticalSliceArchitecture solution template in .NET 10 — GitHub
- Wolverine — a fast, MIT-licensed mediator and messaging library
Related Posts
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.
How to Structure Production Apps With Vertical Slice Architecture in .NET (2026)
A simple, friendly guide to structuring real production .NET 10 apps with vertical slice architecture in 2026 — folders, code, diagrams, and tables.
N-Layered vs Clean vs Vertical Slice Architecture in .NET
A simple, friendly guide comparing N-layered, Clean, and Vertical Slice architecture in .NET, with diagrams, code, tables, and clear advice on when to pick each one.
Building a Modular Monolith With Vertical Slice Architecture in .NET
Learn to build a modular monolith using vertical slice architecture in .NET. Simple words, real-life analogy, diagrams, tables, and clean C# code examples.
The Best Way to Structure .NET Projects: Clean Architecture + Vertical Slices
A friendly guide to structuring .NET projects by mixing Clean Architecture with Vertical Slices, with diagrams, code, tables, and simple, beginner-ready advice.
Where Vertical Slices Fit Inside the Modular Monolith
A simple guide to how vertical slices live inside the modules of a modular monolith in .NET, with diagrams, code, tables, and everyday examples.