Vertical Slice Architecture
Vertical Slice Architecture
"Instead of organizing around technical layers, I organize around features. Instead of a services layer that every feature shares, each feature has all the code it needs, co-located in one place." — Jimmy Bogard, "Vertical Slice Architecture", 2018
Intent
Vertical Slice Architecture is Jimmy Bogard's 2018 formulation of organising code by feature (a "slice" cuts vertically from API to database) rather than by technical layer. The organising question is: what capability does this code serve? — not: what concern does this code handle?
VSA is orthogonal to Clean-Architecture, Hexagonal-Architecture, and Onion Architecture. Those patterns organise code by technical concern (what layer a piece of code belongs to); VSA organises code by feature (what use case a piece of code serves). They are not competing alternatives — a project can use VSA internally within each module of a layered architecture.
VSA does not require CQRS or a mediator. These patterns are common companions because each slice resembles a command or query handler — but VSA is the architectural principle, CQRS-Pattern and Mediator-Pattern are implementation tactics that emerge naturally from slices. A VSA codebase can use plain function calls with no dispatch library.
When NOT to Use
- Under 5 distinct use cases — the overhead of feature folders and slice organisation is not justified; a simple layered structure is easier to navigate and maintain
- Single CRUD entity — if the application is one entity with basic create/read/update/delete, use Clean-Architecture or Hexagonal-Architecture; the added complexity of per-feature organisation serves no purpose
- Team unfamiliar with the domain — slices require understanding feature boundaries before you can draw them; if the team has not yet established a domain model and identified use cases, VSA will produce arbitrary or misaligned slices that are harder to refactor than a flat layered structure
When to Use
- 5 or more distinct use cases with different read and write data shapes
- Teams that navigate code by asking "what feature is this?" rather than "what layer is this?"
- CQRS-Pattern systems where command and query handlers are naturally self-contained slices — each handler maps directly to a vertical slice
- Projects already suffering from "shotgun surgery" — changing one feature requires touching 4 or more files spread across multiple technical layers
- Codebases where features have diverging complexity: some use cases are simple CRUD, others are complex domain workflows; slices let each use case carry exactly the complexity it needs
How It Works
Each slice contains everything needed to complete one use case — handler, DTOs, validation, and a repository interface (or a call to the shared domain). The slice is the unit of cohesion: code that changes together for a feature lives together.
Structure:
src/
shared/
domain/ # shared domain models (Order, Customer, Money)
validation/ # shared validators
infrastructure/ # logging, auth, error handling
features/
place-order/ # everything for PlaceOrder lives here
PlaceOrderHandler.ts
PlaceOrderCommand.ts
PlaceOrderResponse.ts
cancel-order/
CancelOrderHandler.ts
...
The shared/ kernel contains what (domain types, cross-cutting infrastructure); slices own how (the orchestration and workflow for each use case). Slices import from shared/domain/; they do not import from each other's handlers.
Optional boundary enforcement: eslint-plugin-boundaries can prevent cross-slice handler imports, enforcing the rule that slice orchestration logic is not reused across slices.
How It Differs from Layered Architectures
In a layered architecture, the organisation axis is technical concern: controllers in one folder, services in another, repositories in a third. In VSA, the organisation axis is business capability: everything for one use case lives together.
| In layered architecture, PlaceOrder logic lives in... | In VSA, PlaceOrder logic lives in... |
|---|---|
controllers/OrderController.ts (HTTP handling) | features/place-order/PlaceOrderHandler.ts |
application/PlaceOrderUseCase.ts (business logic) | features/place-order/PlaceOrderHandler.ts |
domain/Order.ts (domain model — shared) | shared/domain/Order.ts (still shared) |
repositories/OrderRepository.ts (data access) | features/place-order/OrderRepository.ts (or shared) |
The layered architecture navigation question is: "What layer is this?" The VSA navigation question is: "What feature is this?" They are different questions. A project can answer both by nesting VSA inside a layered module.
Vertical Slice does not mean Share Nothing. Slices share domain models, validation rules, and cross-cutting infrastructure (logging, auth, error handling). What slices must NOT share is handler/orchestration logic — each slice owns its own workflow from request to response.
Jimmy Bogard (the pattern's originator) explicitly refutes the Share-Nothing interpretation: shared domain models are a sign of healthy slices, not a violation. See also: Derek Comartin, "Vertical Slice Architecture Myths You Need to Know" (codeopinion.com).
Architecture Diagram
Vertical-Slice-Architecture-diagram.excalidraw
TypeScript Example
Folder layout:
src/
shared/
domain/ # shared domain models (Order, Customer, Money)
validation/ # shared validators
infrastructure/ # logging, auth, error handling
features/
place-order/ # everything for PlaceOrder lives here
PlaceOrderHandler.ts
PlaceOrderCommand.ts
PlaceOrderResponse.ts
cancel-order/
CancelOrderHandler.ts
...
PlaceOrder slice handler — plain function, no mediator library:
// features/place-order/PlaceOrderHandler.ts
// ALL PlaceOrder logic lives here — no cross-layer navigation required
// Domain model (Order) imported from shared/domain — slices share domain models
export interface PlaceOrderCommand {
customerId: string;
items: Array<{ productId: string; quantity: number }>;
}
export interface PlaceOrderResult {
orderId: string;
}
export interface OrderRepository {
save(order: Order): Promise<string>;
}
// Handler — plain function, no mediator library import
export async function placeOrder(
command: PlaceOrderCommand,
repository: OrderRepository
): Promise<PlaceOrderResult> {
const order = Order.place(command.customerId, command.items);
const orderId = await repository.save(order);
return { orderId };
}Compare with Clean Architecture: the equivalent PlaceOrder code would be split across controllers/OrderController.ts (HTTP), application/PlaceOrderUseCase.ts (orchestration), and domain/Order.ts (domain) — three folders, three files, three navigation hops to understand one feature. In VSA, one folder.
Java Example
Package layout:
com.app/
shared/
domain/ # Order.java, Customer.java
features/
placeorder/ # PlaceOrderHandler.java, PlaceOrderRequest.java
cancelorder/
PlaceOrder slice handler — plain class, no command bus imports:
// features/placeorder/PlaceOrderHandler.java
// ALL PlaceOrder logic co-located — plain handler class, no command bus imports
// Order from shared.domain — slices share domain models
public record PlaceOrderRequest(String customerId, List<OrderItem> items) {}
public record PlaceOrderResponse(String orderId) {}
public interface OrderRepository {
String save(Order order);
}
public class PlaceOrderHandler {
private final OrderRepository repository;
public PlaceOrderHandler(OrderRepository repository) {
this.repository = repository;
}
public PlaceOrderResponse handle(PlaceOrderRequest request) {
Order order = Order.place(request.customerId(), request.items());
String orderId = repository.save(order);
return new PlaceOrderResponse(orderId);
}
}Related Concepts
| Pattern | Relationship |
|---|---|
| Clean-Architecture | Organises by technical layer (what concern); VSA organises by feature (what capability). Orthogonal — a module can use VSA internally within Clean Architecture layers. The Entities and Use Cases from Clean Architecture become the shared/domain/ and per-slice handlers in VSA. |
| Hexagonal-Architecture | Ports and Adapters defines where code lives by dependency direction; VSA defines where code lives by feature. Composable — a Hexagonal module can organise its use cases as vertical slices. |
| CQRS-Pattern | Common companion because each VSA slice resembles a command or query handler. CQRS is an implementation tactic, not a prerequisite for VSA. Each slice naturally becomes either a command handler or a query handler — but this is a consequence, not a requirement. |
| Mediator-Pattern | Often used to dispatch requests to slice handlers (MediatR in .NET is the dominant VSA tooling pattern). The mediator is a convenience — VSA works with plain function calls. Neither the TypeScript nor Java example above uses a mediator library. |
| Bounded-Context | Slice boundaries often align with bounded context boundaries at the feature level. When they diverge, the Bounded-Context boundary takes precedence — slices are a within-context organisation pattern, not a cross-context boundary mechanism. |
| Domain-Events | When one slice needs to trigger another, the correct mechanism is a domain event — not a direct import of the other slice's handler. This prevents cross-slice coupling while allowing workflow coordination. |
Forward references — suitability thresholds for adjacent patterns:
Modular Monolith: justified when bounded contexts are clear and the team needs deployment independence without network overhead. A Modular Monolith can use VSA internally within each module — they operate at different granularities (module boundary vs feature boundary). See Phase 19.
Micro-frontends: justified only when 3 or more independent frontend teams ship to the same product with independent release cadences. Below this threshold, a modular frontend monolith organised by feature (VSA applied to the frontend) is the correct architecture. See Phase 20.
Sources
- Jimmy Bogard, "Vertical Slice Architecture" (2018) — https://www.jimmybogard.com/vertical-slice-architecture/
- Derek Comartin, "Vertical Slice Architecture Myths You Need to Know" — https://codeopinion.com/vertical-slice-architecture-myths-you-need-to-know/
- Robert C. Martin, Clean Architecture: A Craftsman's Guide to Software Structure and Design, Prentice Hall, 2017 — for contrast with layered family
- Alistair Cockburn, "Hexagonal Architecture" (2005) — https://alistair.cockburn.us/hexagonal-architecture/ — for contrast with port-based decomposition
Backlinks
Notes that link here: Design-Patterns-MOC, Application-Architecture-MOC, CQRS-Pattern, Mediator-Pattern, Domain-Events, Component-Composition-Patterns (a vertical slice can use hooks or inject functions as its internal composition strategy)