What does it mean to organize a module's code as 'vertical slices' by feature instead of by technical layer, and what specific problem does that solve compared to a controller/service/repository package split?
answer
- group by feature/use case, not by layer
- one slice = endpoint+logic+data access together
- controller/service/repository split = horizontal layering
- duplication tolerated, rule of three before sharing
- cross-cutting concerns need pipeline/middleware, not a shared service
basics
~10 sInstead of grouping all controllers together, all services together, all repositories together, you group everything one feature needs into a single place, so changing that feature means touching one folder, not five.
solid answer
~50 sA layered split organizes a codebase horizontally: one package for controllers, one for services, one for repositories, so implementing a single feature - say, 'cancel an order' - means touching several packages scattered across the layer structure, and it's hard to tell from the folder tree what the feature actually does. A vertical slice organizes by feature/use case instead: 'CancelOrder' gets its own package containing its endpoint, its logic, and its data access together, and 'ProcessRefund' gets its own separate slice, even if some persistence code looks superficially similar. The payoff is locality of change (a feature's blast radius is one folder) and easier deletion/replacement of a whole use case; the cost is some duplication across slices and losing the single, layer-wide place to enforce a cross-cutting convention, which then needs a different mechanism, such as a pipeline behavior, once you slice vertically.
go deeper
Should be able to describe the basic difference in plain terms: grouping code by feature so one folder contains everything a change needs, versus grouping by type of code.
Should articulate the concrete problem it solves - bloated shared services/repositories accumulating unrelated logic - and name the trade-off of accepted duplication.
Should judge when slicing is worth it versus when it produces harmful duplication of real business rules, and describe a mechanism, such as a pipeline or rule of three, for managing the trade-off.
Should connect vertical slicing to team/ownership dynamics - independent, low-coordination feature delivery - and to its relationship with module-level boundaries, recognizing they solve different problems at different scopes.
## What a vertical slice is A vertical slice is a way of drawing the internal seams inside a module (or an application) along use cases instead of along technical layers. The traditional, horizontal alternative groups code by what kind of thing it is: - a `controllers` package holds every HTTP endpoint; - a `services` package holds every piece of business logic; - a `repositories` package holds every data-access class. To understand or change one feature — say, cancelling an order — a developer has to open the controller package to find the endpoint, the service package to find the logic, and the repository package to find the query, and each of those packages also contains code for every unrelated feature the module supports. The vertical-slice alternative groups code by feature instead: everything the 'cancel order' use case needs — its endpoint, its command/handler, its validation, its query — lives together, often as a single small set of files named after the use case, while 'process refund' has its own separate, self-contained set even though both use cases touch the same underlying `Order` table. ## The problem it fixes This exists to fix a specific, well-known layered-architecture failure mode: shared services and repositories that quietly grow to serve every feature in the module become large, and every new feature is tempted to reuse an existing service method by adding a parameter or a branch to it rather than writing a new one, because that's the path of least resistance in a horizontal layer. Over time, one `OrderService` class accumulates methods for a dozen unrelated use cases, each with subtly different validation and side effects, and a change intended for one feature risks breaking another feature that happens to share the same service method. Vertical slices remove the temptation to share by making 'add a new, small slice' at least as easy as 'extend an existing shared class', so each use case's logic stays independently understandable, testable, and, crucially, deletable: retiring a feature means deleting its slice, not carefully picking apart which lines of a shared service still belong to other features. ## The two costs The trade-off is real, not free. 1. **The most visible cost is duplication**: two slices that both need 'load the order and check the caller owns it' will often each contain their own version of that check rather than sharing one utility method, because sharing across slices reintroduces some of the coupling slicing was meant to avoid. Teams manage this by tolerating a controlled amount of duplication and only promoting logic to a shared, module-level helper once the same code has proven itself needed in three or more slices — a 'rule of three' — rather than abstracting on the first repetition. 2. **The second cost** is that horizontal, layer-wide concerns — a single request-logging interceptor, a global exception mapper, a consistent transaction boundary — lose the one obvious place they used to live and need a different mechanism to stay consistent across every slice: a pipeline/middleware behavior that wraps every handler, a base class or decorator every slice opts into, or convention enforced by tests, rather than a shared service class doing it implicitly. ## Getting it wrong in either direction In production, the failure mode of getting this wrong in either direction is visible. - **Slicing badly** — too finely, or duplicating genuinely stable domain logic rather than superficial query shapes — produces a codebase where the same business rule, say 'an order under $10 doesn't need manager approval to cancel', is copy-pasted into five slices, and a rule change becomes a five-place hunt-and-fix that is easy to get inconsistent, effectively recreating the shared-service coupling problem in a different shape. - **Failing to slice at all**, keeping a purely horizontal split inside a module that has grown to a dozen or more use cases, produces the opposite and more common failure: a bloated `OrderService` where an engineer touching 'cancel order' logic has to read and reason about unrelated 'place order' and 'apply discount' code paths just because they happen to live in the same file, and where the blast radius of any one change is hard to bound. ## Where the name comes from, and what it is not Vertical slices are commonly associated with the 'Vertical Slice Architecture' name popularized in the .NET community, often paired with a mediator library so each slice is literally one command/handler pair, but the same idea — organizing a Spring Modulith module's internal packages by use case rather than by controller/service/repository — applies equally on the JVM, and is orthogonal to the module-level boundary enforcement used between modules: - **vertical slicing** is about structure inside a module; - **the module boundary** is about the contract between modules.
- If two vertical slices need nearly identical validation logic, when should you extract it into a shared helper versus leaving it duplicated?A common heuristic is to wait until the same logic has genuinely appeared in three or more slices (rule of three) before extracting it, because premature sharing after just two occurrences often turns out to be a coincidence rather than a real shared concern, and the extracted helper then has to grow awkward parameters to handle both callers' slightly different needs. Waiting also avoids re-coupling slices that vertical slicing was meant to keep independent.
- How do you keep a cross-cutting concern like transaction boundaries or request logging consistent across many vertical slices without a shared service layer?Typically through a pipeline/middleware mechanism that every slice's handler passes through automatically - a mediator pipeline behavior, a decorator applied by convention, or a framework-level interceptor/aspect - so the concern is applied uniformly without each slice's author having to remember to call it, and without reintroducing a shared class that every slice's business logic depends on.
- Does vertical slicing conflict with the module-boundary enforcement used between Spring Modulith modules?No, they operate at different scopes and compose fine: module boundaries govern what one module exposes to other modules, while vertical slicing governs how code is organized inside a single module's internals. A module can slice its internal use cases vertically while still presenting one coherent public API to the rest of the application.
Like organizing a toolbox by project kit (everything needed for 'fix the sink' in one pouch) instead of by tool type (all wrenches in one drawer, all screwdrivers in another) - grabbing one pouch gets you everything for that job, even though it means owning a second wrench in a second pouch instead of sharing the one drawer wrench across every job.
saying these in an interview costs you the question
- thinks vertical slicing means every feature gets its own database or deployable
- believes duplication across slices is always a bug to eliminate on sight
- can't name a downside of vertical slicing, treating it as strictly superior with no trade-off
- confuses vertical slicing (structure inside a module) with module boundaries (contract between modules)
- assumes a shared service layer is unnecessary for literally any cross-cutting concern