Playbook

How Does a Vertical Slice Work from the Inside? (How Does A Vertical Slice Work From The Inside)

A technical guide to the inner flow of a Vertical Slice: request, validation, handler, aggregate, outbox, projection, idempotency, performance, tests, and…

Vertical Slice — Feature-First Engineering

Part 2 of 4

A vertical slice request pipeline from endpoint to handler, validation, persistence, event publication, and tests

Summary in 30 seconds

A Vertical Slice is not a folder naming convention. It is the boundary that keeps one user intent together from request, validation, authorization, handler, domain behaviour, persistence, event publication, projection, and tests.

User intent
  -> Endpoint
  -> Command / Query
  -> Validation
  -> Handler
  -> Domain behaviour
  -> Transaction
  -> Outbox event
  -> Projection / Read model
  -> Tests and observability

The goal is not to make code look vertical. The goal is to contain change, failure, testing, and cost inside the right feature boundary.

The concrete problem

Imagine adding a delivery note to an order in a marketplace. It looks like one string field, but production quickly pulls it into checkout summary, merchant operations, shipping integration, customer email, and audit records. The architectural question becomes: which user intent owns this behaviour, and where must consistency be completed?

Concepts when they first appear

Command
An intent asking the system to perform behaviour. It can fail.

Query
A side-effect-free request to read data.

Handler
The application-level orchestrator for one command or query.

Pipeline behaviour
A chain around the handler for validation, authorization, transaction, logging, and idempotency.

Outbox
A pattern that stores the business change and the publishable event in the same local transaction.

Projection
A read model shaped for a screen or query pattern.

These parts are not interchangeable. The handler orchestrates; the domain model owns rules. The pipeline protects common application concerns. The outbox records events that must not be lost.

Story: the delivery note slice

A practical slice can look like this:

Features/Orders/AddDeliveryNote
  AddDeliveryNoteEndpoint.cs
  AddDeliveryNoteCommand.cs
  AddDeliveryNoteValidator.cs
  AddDeliveryNoteHandler.cs
  AddDeliveryNoteResponse.cs
  AddDeliveryNoteTests.cs

Each file has one reason to exist. The endpoint owns the HTTP contract, the command states intent, the validator protects the input boundary, the handler coordinates the use case, the aggregate enforces business rules, and the tests prove behaviour.

Algorithm design

algorithm AddDeliveryNote(command):
  input: orderId, customerId, note, requestId
  output: AddDeliveryNoteResult

  validate command
  reject if requestId was already processed

  order = orderRepository.load(orderId)
  reject if order does not exist
  reject if order.customerId != customerId

  order.addDeliveryNote(note)
  outbox.add(DeliveryNoteUpdated(orderId, note, occurredAt))

  transaction.commit()
  return success(orderId, note)

The normal command path is O(1): one aggregate lookup, a bounded set of rules, and a bounded number of events. Idempotency backed by a unique index is O(log n); a key-value store can make the average lookup O(1). Memory usage is O(1) because the handler does not load an unbounded history.

Why this becomes a design problem

Layered code can keep classes small while still splitting one business decision across multiple places.

Controller seems to validate
Service seems to own rules
Repository seems to guard data
Frontend checks again
Tests protect call order

That is not behavioural Single Responsibility. Vertical Slice moves SOLID from class naming into ownership of change. Interface Segregation means narrow ports such as IOrderRepository, IOutboxWriter, and IRequestDeduplicationStore, not a broad IOrderService that carries every use case.

CQRS and the mediator pipeline

CQRS makes intent explicit. A command requests behaviour; a query reads without side effects. A mediator is useful only if it keeps repetitive application concerns outside handlers: validation, authorization, transaction, logging, and idempotency. Library choice should still be reviewed for licensing, AOT support, reflection cost, and enterprise approval.

Alternatives and decision

Alternative Strength Accepted cost
Simple CRUD service Lowest starting cost Shared service grows with behaviour
Vertical Slice without CQRS Good feature locality Intent separation is less explicit
Vertical Slice with CQRS Clear intent, tests, and pipeline More contracts and discipline
Physical read/write separation Independent read performance Eventual consistency and operations cost
Microservice Independent deployment and scale Network, data, and observability cost

My default decision is staged: prove the feature boundary inside a modular monolith first; split read models, broker flows, cache, or deployment units only when metrics justify the cost.

Data separation and outbox

A read replica is not CQRS. A replica repeats the same schema; a projection reshapes data for the query. A delivery-note projection may store the exact fields a seller dashboard needs, avoiding repeated joins on every request.

The dangerous flow is writing the database and then publishing to the broker as two independent actions. If the broker call fails after commit, the order exists but the event does not. Transactional Outbox closes that gap by storing the business record and event in one local transaction. Since delivery is usually at least once, consumers must remain idempotent.

Cost, performance, and tests

Vertical Slice also exposes the unit of scale. Checkout commands may need strong consistency and short transactions; catalog queries may need cache, projection, and high concurrency. Each cache, NAT gateway, load balancer, queue, read store, and deployment unit adds real cost, so the decision must follow p95 latency, read/write ratio, connection saturation, egress, retry volume, and blast radius.

A hot command path should avoid unnecessary allocations. A query should read only the required columns and page by cursor when possible. Listing p items is O(p) in time and memory; loading the whole dataset is O(n) memory and usually the wrong production behaviour.

Test strategy

  1. Validator tests cover boundary values.
  2. Handler tests cover success, missing order, ownership failure, and duplicate request.
  3. Domain tests protect aggregate invariants.
  4. Integration tests prove transaction and outbox are committed together.
  5. Architecture tests prevent one slice from depending on another slice's internals.
  6. Projection tests prove duplicate events are harmless.

The goal is not to mock every class. The goal is to find the broken decision quickly.

Common confusions

❌ Vertical Slice means putting controller-to-repository files in one folder
✓ It means designing the behaviour, data, and test boundary around one user intent.

❌ CQRS always means separate databases
✓ CQRS starts as intent separation; physical separation is a separate cost decision.

❌ Mediator is the architecture
✓ Mediator is dispatch and pipeline plumbing; ownership defines the boundary.

❌ Outbox gives exactly-once delivery
✓ Outbox prevents event loss; consumers still need idempotency.

Decision checklist

  1. Does the slice represent one user intent?
  2. Does the handler orchestrate instead of hiding domain rules?
  3. Are command and query models separated when their reasons for change differ?
  4. Is the transaction boundary explicit?
  5. Is event publication tied to the database write through outbox?
  6. Are consumers idempotent under duplicate delivery?
  7. Does the projection reduce a real query cost?
  8. Is extra infrastructure justified by latency, cost, or blast-radius evidence?
  9. Are interfaces narrow and use-case oriented?
  10. Do architecture tests protect feature boundaries?

What should remain with you

A mature Vertical Slice manages the economics of change. CQRS, mediator pipelines, outbox, and projections are separate tools; each must earn its cost. Start with a logical feature boundary, then add physical separation only when production evidence supports it.

Next, we will examine how Vertical Slice, DDD aggregates, and modular monolith boundaries stay protected in production.

FAQ

Frequently asked questions

Is it true that "Vertical Slice means putting controller-to-repository files in one folder"?

It means designing the behaviour, data, and test boundary around one user intent.

Is it true that "CQRS always means separate databases"?

CQRS starts as intent separation; physical separation is a separate cost decision.

What is "How Does a Vertical Slice Work from the Inside?" about?

A technical guide to the inner flow of a Vertical Slice: request, validation, handler, aggregate, outbox, projection, idempotency, performance, tests, and…

Engineering Principles Learned

  • The true boundary of a slice is the user intent and consistency decision that change together.
  • CQRS and outbox expose intent and reliability cost; they do not require physical separation by default.
  • Production architecture is measured through p95 latency, I/O, egress, retry volume, and rollback cost.

Continue reading

Continue reading

Next in series

Related articles

Related articles

Paylaş