Vertical Slice vs Clean Architecture ā the Trade-offs
Understand what vertical slice architecture is, how it differs from Clean Architecture, when to choose it, and what the real trade-offs look like in a .NET codebase.
Vertical Slice Architecture
Vertical Slice organizes code around features rather than technical layers. Instead of Controllers ā Services ā Repositories, each feature owns its full stack: request, handler, and data access in one place.
The Problem with Layered Architecture
In Clean Architecture, adding "Create Order" touches:
OrderController.cs(Presentation)CreateOrderCommand.cs(Application)IOrderService.cs(Application interface)OrderService.cs(Application implementation)IOrderRepository.cs(Domain interface)OrderRepository.cs(Infrastructure)
Six files across four layers ā for one feature.
Vertical Slice Structure
Features/
āāā Orders/
ā āāā CreateOrder/
ā ā āāā CreateOrderCommand.cs ā request model
ā ā āāā CreateOrderHandler.cs ā all logic here
ā ā āāā CreateOrderEndpoint.cs ā HTTP binding
ā āāā GetOrder/
ā ā āāā GetOrderQuery.cs
ā ā āāā GetOrderHandler.cs
ā āāā ListOrders/
ā āāā ListOrdersQuery.cs
ā āāā ListOrdersHandler.cs
āāā Products/
ā āāā ...Everything for one feature is in one folder.
A Vertical Slice in Practice
// Features/Orders/CreateOrder/CreateOrderCommand.cs
public record CreateOrderCommand(int CustomerId, List<OrderLineDto> Lines);
public record CreateOrderResponse(int OrderId, decimal Total);
// Features/Orders/CreateOrder/CreateOrderHandler.cs
public class CreateOrderHandler(AppDbContext db) : IRequestHandler<CreateOrderCommand, CreateOrderResponse>
{
public async Task<CreateOrderResponse> Handle(CreateOrderCommand cmd, CancellationToken ct)
{
// Validation, business logic, persistence ā all here
var order = new Order
{
CustomerId = cmd.CustomerId,
Lines = cmd.Lines.Select(l => new OrderLine
{
ProductId = l.ProductId,
Quantity = l.Quantity,
UnitPrice = l.UnitPrice,
}).ToList()
};
order.Total = order.Lines.Sum(l => l.Quantity * l.UnitPrice);
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return new CreateOrderResponse(order.Id, order.Total);
}
}
// Features/Orders/CreateOrder/CreateOrderEndpoint.cs
public static class CreateOrderEndpoint
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapPost("/orders", async (CreateOrderCommand cmd, IMediator mediator) =>
{
var result = await mediator.Send(cmd);
return Results.Created($"/orders/{result.OrderId}", result);
}).RequireAuthorization();
}
}Vertical Slice vs Clean Architecture
| | Vertical Slice | Clean Architecture | |---|---|---| | Organization | By feature | By layer | | Coupling | Feature-isolated | Layer-isolated | | Adding a feature | One folder | Multiple layers | | Shared code | Shared/ folder | Domain layer | | Abstraction | Per-feature | Cross-cutting | | Best for | Feature-heavy apps | Domain-heavy apps |
When to Choose Vertical Slice
Good fit:
- CRUD-heavy APIs where features are mostly independent
- Teams working on different features in parallel
- Apps where the domain model is thin
- When you want to avoid premature abstraction
Less suited:
- Complex domain logic that's shared across many features
- When you need strict layer enforcement for compliance
- Very small apps ā the overhead isn't worth it
Shared Code
Not everything belongs in a feature. Use a Shared/ folder:
Shared/
āāā Abstractions/ ā interfaces used across features
āāā Behaviors/ ā MediatR pipeline behaviors (logging, validation)
āāā Errors/ ā common error types
āāā Persistence/ ā DbContext, migrations
āāā Extensions/ ā extension methodsKey Takeaways
- Vertical Slice organizes by what (feature) not where (layer)
- Each slice owns its full stack ā request, logic, and data access in one place
- MediatR is the glue ā commands/queries decouple the endpoint from the handler
- Shared code goes in
Shared/ā resist the urge to add cross-cutting abstractions too early - Neither architecture is objectively better ā choose based on your team size, domain complexity, and how features relate to each other
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.