Vertical Slices Over Horizontal Layers
What This Concept Is
A vertical slice is a unit of work that crosses every layer your system has but only addresses one small piece of user-visible behavior. A horizontal layer, by contrast, is a unit of work that finishes one layer across many behaviors before moving on.
Given the capstone's layers (API -> service -> repository -> database plus tests, migrations, and deploy config), the difference is:
- Vertical: ship
POST /tasksend-to-end, including its service method, its repository method, its migration, its test, and its deploy -- then move to the next feature. - Horizontal: design all the API endpoints, then all the services, then all the repositories, then all the migrations, then wire them together.
Vertical slicing is the default for modern delivery because it keeps a working system working. It also maps naturally onto trunk-based development, small PRs, and Fowler's keystone interface pattern: ship the one path that the next feature can be hung from.
A well-cut slice is independently shippable. That is the acid test. If cutting feature A into slices produces pieces that can be deployed in any order without leaving the system broken, the slicing is right. If one slice requires three others before it makes sense, you have not sliced; you have sequenced.
Why It Matters Here (In the Capstone)
Horizontal layering is seductive in a capstone because it feels organized. In practice it has three failure modes:
- integration risk stacks up at the end, when debugging is hardest and deadlines are closest;
- no feature is shippable until the entire stack is complete, so early demos are impossible;
- mistakes in layer N are not found until layer N+2, because there is nothing exercising them.
Vertical slicing forces you to feel friction from every layer on every feature. That friction is information, not punishment. It tells you where the walking-skeleton baseline is weak and where your design has not met reality yet.
This module depends on the walking skeleton being already in place. Once the seams are proven, each new vertical slice adds a small piece of user-visible behavior through those seams, and each slice's value is measurable as something a user or test can observe.
Concrete Example(s) -- from a real capstone
Take the capstone REST service again. Two ways to build the first real feature, "create and list tasks":
Horizontal approach (discouraged):
- Week 1: design every endpoint (
/tasks,/users,/projects) - Week 2: implement every service class
- Week 3: implement every repository
- Week 4: run migrations, wire everything, fix what does not compile, then finally test
Vertical approach (encouraged):
- Day 1: ship
POST /tasksthat writes one row and returns 201, plus one integration test - Day 2: ship
GET /tasksthat lists the rows, plus one test - Day 3: ship
GET /tasks/{id}, plus one test - Day 4: ship an error case for
POST /taskswith invalid input - Only then: move to users, or to projects, or to auth
After day 1 you already have a deployable system that does real work for one user story. After day 2 you have a demoable flow. If the deadline moves in, you have a shippable subset at every point -- a property that horizontal delivery cannot offer until the very end.
Common Confusion / Misconceptions
The first misconception is that vertical slicing means "skip design." It does not. The architecture and layering decisions (from Module 1) are exactly what make vertical slices cheap. A slice is small because the seams are in the right place.
The second is that a vertical slice must be trivial. A slice can be as narrow or as wide as is sensible, as long as it crosses every layer and produces observable behavior. "Add server-side validation to POST /tasks" is a legitimate slice.
The third is confusing vertical slicing with "one big pull request per feature." Slices are units of work, not units of merge. You can slice a feature into three commits or three PRs; the key is that each commit leaves the system in a shippable state.
The fourth is the "fake vertical" -- the slice crosses layers on paper, but some layers are stubs (the persistence layer writes to a dict in memory, the external call is mocked). That is still horizontal: the risky layer is not real yet.
How To Use It (In Your Capstone)
For every planned chunk of work, ask and answer these in order:
- Can I cut this into pieces that each cross every layer?
- Does each piece produce something a user (or a test) can observe?
- After each piece, is the main branch still deployable?
- Which piece do I ship first, and what does it prove?
- If I cannot ship this today, which piece is the next smallest one that still crosses every layer?
- Is the slice behind a feature flag (keystone interface) so the merge is safe even if the UI is incomplete?
- Did I update one test at each level exercised by the slice?
If a chunk cannot be sliced vertically, treat that as a design signal. Usually the layers are too coupled, the seams are in the wrong place, or the feature should be smaller.
A slice is "right-sized" when one person can finish it in a session (2-4 hours), it crosses every architectural layer the final feature will cross, it leaves main deployable, and it produces one observable outcome. If a slice is larger than a session, cut it. Common cut lines: split read from write, split happy path from validation, split persistence from presentation, split collaborators (auth first, feature behind auth).
Anti-Patterns to Recognize
- Fake vertical. Persistence or external calls stubbed; the slice looks vertical but the risky seam is not real.
- Refactor slice. An internal restructuring billed as a slice with no observable output. File as refactor work.
- Shadow layer. Works in dev but not in staging because a deploy seam was skipped.
- Too-narrow slice. So trivial it does not exercise the seams (constant-string endpoint).
See also (integrative)
- S3 M05 Applied Design & Code Review -- small, reviewable commits map 1:1 onto vertical slices
- S3 M02 Refactoring Techniques -- when slicing reveals seams that are in the wrong place
- S6 M04 Transactions & Consistency -- why vertical slices must stay atomic across layers
- S7 M04 API Design & Contract Evolution -- how slice-by-slice delivery evolves public contracts without breaking them
- S10 M01 Domain Analysis & Architecture Design -- the architecture the slices are traversing
External references:
- Martin Fowler: The Practical Test Pyramid -- how each slice gets tested at multiple levels
- Martin Fowler: Keystone Interface -- hanging the next feature off a thin vertical spine
- Martin Fowler: Feature Toggles (Feature Flags) -- shipping a slice before its consumers are ready
- Kent Beck: Test Desiderata -- properties that good slice-level tests share
Check Yourself
- Why does a horizontal approach concentrate risk at the end of the schedule?
- What is the minimum a vertical slice must produce, and what observable artefact proves it produced it?
- What does it mean if a feature resists being sliced -- is that a feature problem, a design problem, or both?
- How do you tell the difference between a real slice and a "fake vertical"?
- How does a feature flag or keystone interface let you ship slices before their consumers are ready?
Mini Drill or Application (Capstone-scoped)
- Take your capstone backlog and pick the first feature after the walking skeleton. In 20 minutes, cut it into at least three vertical slices, and write the user-observable behavior, layers touched, and one-sentence test for each slice.
- Commit the slice-plan to
library/raw/slice-plan-<feature>.mdin your repo so reviewers can see the sequencing. - Ship slice one today. Prove with a staging deploy and a screenshot or response payload.
- Put the next two slices behind a feature flag (keystone interface style) and merge them before their UI is complete.
- At the end of the week, retrospect: did any slice expand beyond its budget? If yes, what seam forced the growth, and does it belong in the debt ledger (Concept 15)?
Source Backbone
Capstone implementation applies earlier code-quality, testing, and refactoring material. These books are the source backbone for that practice.
- Software Engineering at Google - testing, review, and engineering-process backbone.
- Refactoring - safe change and behavior-preserving improvement.
- Good Code, Bad Code - maintainability and code-quality judgment.
- Clean Code - readability and function-level craft support.