Choose Vertical Slice Architecture when your features are mostly independent, the application is CRUD-heavy, you want fast iteration with minimal ceremony, or your team works on many features in parallel. Layers still win when many features share complex business logic or you need strict compile-time boundaries. Here are the concrete signals for each, and what to do when they point both ways.
Layered architecture is the default choice in .NET, and defaults rarely get questioned.
The Problem With Layers
In a layered architecture, a simple "Get Order by ID" feature touches:
OrdersController(Presentation)IOrderService+OrderService(Application)IOrderRepository+OrderRepository(Infrastructure)OrderDto,OrderResponse(Mapping)
Seven types across four layers for a database query that returns one object. Adding a new feature means touching every layer, and modifying one means jumping between folders.
What Changes Together Should Live Together
Vertical Slice Architecture organizes code by feature instead of layer, and it's easier to adopt than most people think:
// Layered: related code is scattered
Controllers/OrdersController.cs ← GetOrder, CreateOrder, DeleteOrder
Services/OrderService.cs ← GetOrder, CreateOrder, DeleteOrder
Repositories/OrderRepository.cs ← GetOrder, CreateOrder, DeleteOrder
// VSA: related code is co-located
Features/Orders/GetOrder.cs ← everything for GetOrder
Features/Orders/CreateOrder.cs ← everything for CreateOrder
Features/Orders/DeleteOrder.cs ← everything for DeleteOrder
When you work on "Get Order," you open one file. When you review a pull request, the diff shows one file per feature.
When to Choose VSA
Your Features Are Independent
If most features don't share logic, VSA reduces unnecessary abstractions:
// This feature needs no repository, no service layer
public static class GetOrder
{
public sealed record Query(Guid Id);
public sealed record OrderResponse(
Guid Id, string Status, decimal TotalAmount, DateTime CreatedAt);
public sealed class Handler(ApplicationDbContext db)
{
public async Task<OrderResponse?> Handle(
Query query, CancellationToken ct)
{
return await db.Orders
.Where(o => o.Id == query.Id)
.Select(o => new OrderResponse(
o.Id, o.Status, o.TotalAmount, o.CreatedAt))
.FirstOrDefaultAsync(ct);
}
}
}
The handler queries ApplicationDbContext directly and projects the result into OrderResponse.
Your Team Is Growing
With layers, two developers working on separate features often edit the same controller and the same service, so merge conflicts are frequent.
With VSA, each developer works in separate files. Feature A doesn't touch Feature B's code.
You Want Fast Iteration
VSA has less ceremony. Adding a new feature means creating one file that defines the request, handler, and endpoint. You don't define an interface or implement a repository, and assembly scanning handles the registration.
Your Application Is CRUD-Heavy
Many business applications are variations of create-read-update-delete. VSA handles this cleanly:
Features/
Products/
CreateProduct.cs ← 60 lines
GetProduct.cs ← 40 lines
GetProducts.cs ← 50 lines
UpdateProduct.cs ← 70 lines
DeleteProduct.cs ← 30 lines
Each file is small and self-contained.
You're Using CQRS
CQRS and VSA are a natural pair. Commands and queries are already separate operations, so each one can go in its own file:
Features/
Orders/
Commands/
PlaceOrder.cs
CancelOrder.cs
Queries/
GetOrder.cs
GetOrders.cs
When to Choose Layers
You Have Complex Shared Business Logic
If 10 features all need the same pricing calculation, a PricingService in a service layer makes sense. Duplicating that logic across 10 slices is worse.
You Need Strict Architectural Boundaries
Layers in separate projects enforce compile-time boundaries through project references.
A domain project that doesn't reference the data access project cannot use its DbContext.
VSA in a single project doesn't stop a handler from referencing anything else in the project.
Your Team Is Familiar With Layers
If your team knows layered architecture well and productivity is good, switching to VSA for the sake of switching creates churn without value.
You Have a Rich Domain Model
Domain-Driven Design with a rich domain model benefits from a dedicated domain layer. The domain layer contains complex business rules that multiple features share. Clean Architecture is a better fit here.
The Middle Ground
You don't have to be all-in on either approach. This layout combines both:
src/
MyApp.Api/
Features/ ← Vertical slices for individual operations
Orders/
PlaceOrder.cs
GetOrder.cs
Domain/ ← Shared domain entities (from Clean Architecture)
Order.cs
Customer.cs
Shared/ ← Cross-cutting concerns
Behaviors/
ValidationBehavior.cs
Use slices for application logic, a domain layer for shared business rules, and pipeline behaviors for cross-cutting concerns.
Migrating From Layers, Incrementally
Choosing VSA doesn't mean rewriting your layered application. The migration path I recommend:
- New features go in a
Featuresfolder as self-contained slices. Don't touch the existing layers yet. - When you modify an existing feature, consider moving it into a slice as part of the change. The controller action, service method, and repository method collapse into one handler.
- Leave stable code alone. A feature nobody has touched in a year gains nothing from being restructured.
- Delete layers as they empty out. When
OrderServicehas one method left, inline it and remove the class.
After a few months you have a codebase that's mostly slices with a small legacy core, and you got there without a big-bang rewrite or a feature freeze.
Decision Matrix
Signals that point toward Vertical Slice Architecture:
- Features are largely independent of each other
- The application is CRUD-heavy
- You need fast iteration with minimal ceremony
- The team is growing and works on many features in parallel
- You're already using CQRS
Signals that point toward layers:
- Heavy shared business logic across features
- A rich domain model with DDD
- You need strict compile-time boundaries
- The team is experienced and productive with layers
If your signals land on both sides, that's normal. It usually means the hybrid approach (slices plus a shared domain layer) is your answer.
Summary
Choose Vertical Slice Architecture when:
- Features are independent and rarely share logic
- Your team needs parallel development without merge conflicts
- You value simplicity: one file per feature, no unnecessary abstractions
- You're already using CQRS (with or without MediatR)
- The application is CRUD-heavy without complex domain logic
Choose layers when shared business logic, strict boundaries, or DDD richness justifies the overhead.
Thanks for reading, and stay awesome!
Frequently Asked Questions
When should you use Vertical Slice Architecture?
When features are mostly independent, the application is CRUD-heavy or varied in complexity, you want fast iteration with minimal ceremony, and your team benefits from working in parallel without merge conflicts. CQRS-based apps map onto slices especially well.
When is layered architecture the better choice?
When many features share complex business logic, you need compile-time boundaries between layers, or you are building a rich domain model with DDD. A dedicated domain layer earns its keep when rules are shared across many use cases.
Is Vertical Slice Architecture good for large projects?
Yes, if you group slices by domain area as the feature count grows. Large codebases often combine slices for application logic with a shared domain layer for business rules.
Does Vertical Slice Architecture work without MediatR?
Yes. The pattern organizes code by feature and does not depend on any library. Minimal API endpoints calling plain handler classes achieve the same result.



