BUZZSOFTWARE
SolutionsWorkTechnologiesAboutArticles
ENEnglishRORomână
Get a Quote
BUZZSOFTWARE

We build high-performance software that scales with your business.

Company

  • About
  • Solutions
  • Technologies
  • Our Process
  • Contact

Resources

  • Articles
  • Case Studies
  • FAQ
  • Request a Quote

Legal

  • Privacy Policy
  • Terms of Service

© 2026 BuzzSoftware. All rights reserved.

All posts
May 18, 2026·9 min read·BuzzSoftware Team

Vertical Slices over Layered Architecture: Why Feature-First Folders Win in 2026

ArchitectureVertical SlicesSoftware DesignModularizationAI Development

Open a 2018-era enterprise codebase and the folder layout tells you everything before you read a line of code: controllers/, services/, repositories/, models/, dtos/, validators/. To touch a single feature, you navigate five directories. To understand it, you open seven files. This is layered architecture — also called "clean architecture" or "onion architecture" depending on which book the team read — and for two decades it was the default for serious backend work.

In 2026, the default is shifting. The teams shipping fastest organize their code by feature, not by technical role. The pattern has had a few names — vertical slice architecture, feature-first folders, modular monolith — and in the AI-assisted era it is quietly winning because the economics of code have changed.

Why layered architecture made sense

Layered architecture optimized for a specific problem: humans writing every line of code, and humans reading it for review and maintenance. The benefits were real:

  • Consistency — a UserController looks like an OrderController; once you know one, you know all of them.
  • Reusable infrastructure — repository classes, base controllers, generic services could be shared across features.
  • Testability — each layer mocked the layer below it cleanly.
  • Separation of concerns — domain logic was protected from HTTP and database concerns.

The cost was navigation. Touching a feature meant editing five files in four directories. For a team of senior engineers who held the whole shape in their heads, that cost was tolerable.

What changed

Two things made vertical slices viable, and then preferable.

One: testing got cheaper across the slice. Integration testing with real databases (testcontainers, ephemeral Postgres, SQLite in-memory) became fast enough that mocking the data layer is no longer the default. You can test a slice end-to-end without paying the layered-architecture mocking tax.

Two: AI assistance changed which costs hurt. Generating code is now cheap. Locating code is what dominates the cost of every change. A layered codebase forces the model — and the human — to chase a feature across five files. A vertical slice puts everything in one folder, in one context, where the model can load it all and reason about it together.

What a vertical slice looks like

A slice for a "quote draft" feature might look like:

src/features/quote-draft/
├── quote-draft.types.ts       // domain types, value objects
├── quote-draft.schema.ts      // validation (zod or similar)
├── quote-draft.db.ts          // database access for this slice
├── quote-draft.service.ts     // business logic
├── quote-draft.api.ts         // HTTP handler
├── quote-draft.test.ts        // integration tests
└── quote-draft.ui.tsx         // UI component (if monorepo / Next.js)

One folder. One concept. Everything you need to understand, modify, or delete the feature lives together. Deleting the feature is rm -rf src/features/quote-draft/ plus removing one route entry.

For an AI agent, this is the difference between "load 12K tokens of context to understand the feature" and "load 80K tokens including unrelated infrastructure." For a human reviewer, it is the difference between three files in one tab and twelve files across five tabs.

What you give up

Honesty matters here. Vertical slices are not free.

  • Cross-feature reuse takes intent. Two features both need money formatting? You either duplicate (small file, OK) or extract to a shared module (extra step). With layered architecture, the shared service was the default.
  • Inconsistency creeps in. Slice A authenticates with one pattern, slice B with another, because there is no central AuthService enforcing the contract. Mitigation: shared primitives in src/lib or src/core for the truly cross-cutting concerns (auth, logging, tracing, db client, i18n).
  • Some teams find the duplication uncomfortable. Three slices each define their own formatMoney function. The "DRY police" file a refactor PR. Resist this if the functions actually differ in subtle ways (different locales, different precision rules). Duplication that is honest about not being shared is better than the wrong abstraction.

How to migrate from layered to slices

Do not big-bang. Migrate one feature at a time:

  1. Pick a small, well-understood feature. Something with two endpoints and one model is ideal.
  2. Create src/features/<feature-name>/ and copy the relevant code there from the existing layered structure.
  3. Update the imports so the rest of the codebase reaches the feature through one entry point (a barrel index.ts).
  4. Move the integration tests into the slice; verify they still pass.
  5. Delete the old layered files for that feature.
  6. Repeat for the next feature.

After three or four slices, the team will have an intuition for what belongs in a slice versus what belongs in a shared library. The boundary becomes obvious through use.

What stays shared

Vertical slices do not mean every feature owns its own database client and HTTP framework. The genuinely cross-cutting concerns stay in src/lib/ or src/core/:

  • Persistence primitives — the database client, migration runner, transaction helpers
  • Observability — logger, tracer, metrics emitter
  • Auth middleware — the function that validates a JWT, attaches the user to the request
  • HTTP framework configuration — error handling, request parsing, CORS
  • Truly universal value objects — Email, MoneyAmount, Url, Iso8601Date

Everything else lives in the slice that uses it.

How slices interact

Two patterns work; one antipattern hurts.

Pattern: typed module boundaries. Each slice exports a small, intentional API through its index.ts. Other slices import only what is exported. The slice's internals are private by convention. ESLint can enforce this with import/no-restricted-paths.

Pattern: event-driven coupling. When slice A wants to react to something in slice B (order-created → send-confirmation-email), it does so through an event bus, not a direct call. The slices stay decoupled; the agent does not have to load the email slice to work on orders.

Antipattern: deep imports. import { internalHelper } from "@/features/billing/some-private-file" reaches into another slice's guts. This is the slow death of any slice architecture. Forbid it at the lint level on day one.

What this means for AI-assisted teams

The most concrete benefit shows up the first time you ask an agent to "add a refund button to the order detail page." With slices, the agent can load the order slice — types, service, API, UI — into one coherent context and make the change. The diff is local. The review is fast.

With layered architecture, the agent loads the controller (one file), the service (another), the repository (a third), the DTO (a fourth), and the validation rules (a fifth). The context bloats; the agent's accuracy drops. Reviewers see a sprawling diff and trust it less.

A small team of three engineers using vertical slices and AI assistance routinely ships what a ten-person team of two years ago shipped with layered architecture. The architecture is not the only reason — but the absence of navigation tax compounds across every feature, every reviewer, and every agent session.

If your codebase is still strictly layered, you do not need to rewrite it. Start with the next new feature. Build it as a slice. See how the team feels. The migration, if you decide to do it, can take six months and ship continuously — there is no rewrite weekend.

The architectural choice is not religious. It is economic. In 2026, when locating code costs more than writing it, slices win.

Bring us the messy part.

Send a paragraph about what you're trying to build. We come back inside 48 hours with a scope, a stack, and a price.

Start a projectOr browse what we do →

All posts

Agentic Control Layers: Production Reliability Patterns for LLM Agents

10 min read

Multi-Hop RAG at Scale: Inside Teilor's Opal Knowledge Base

11 min read

GEO for SaaS in 2026: How to Get Cited by AI Answer Engines

9 min read