Vertical Slicing vs Horizontal Layers: The Directory Structure Civil War

Vertical Slicing vs Horizontal Layers: The Directory Structure Civil War

Why your DDD folder structure sparks more debate than the actual business logic, and how Feature Sliced Design is challenging decades of layered architecture orthodoxy.

The software industry has finally achieved the impossible: we’ve made directory structure controversial. While developers once argued about tabs versus spaces or Vim versus Emacs, today’s battlefield is the src folder. Specifically, whether you organize by technical layer (domain, infrastructure, application) or by vertical feature slices (entities, features, pages). This isn’t just aesthetic preference, it’s a fundamental disagreement about how software evolves, how teams scale, and what “separation of concerns” actually means when you’re debugging at 2 AM.

A recent Reddit thread captured this paralysis perfectly. A developer studying Eric Evans’ Domain-Driven Design “blue book” found themselves frozen at the IDE, wondering if a “by the book” implementation means src/domain and src/infra directories, or whether DDD permits the heresy of src/foo/domain and src/foo/infra. The responses ranged from pragmatic to exhausted, with several noting that Evans’ original text is notoriously dense and suggesting Vaughan Vernon’s more practical implementations as an alternative. The underlying anxiety is clear: we’re terrified of organizing our code “wrong”, as if the compiler cares about our folder names.

Conceptual diagram comparing vertical slicing vs horizontal layers architecture in directory structures
Directory structure approaches: Feature Sliced Design versus traditional layered architecture.

The Horizontal Orthodoxy: Separation by Technical Concern

Traditional layered architecture, the kind taught in computer science programs and enterprise architecture certifications, organizes code by technical responsibility. You know the drill: domain/ holds your entities and value objects, application/ contains use cases and DTOs, infrastructure/ deals with databases and external APIs, and presentation/ handles UI concerns. The theory is elegant. Dependencies point inward toward the domain, following the Dependency Inversion Principle. The domain remains pure, untainted by infrastructure concerns.

The reality? Changing a single user story requires opening twelve different folders. Want to add a “reserve inventory” feature? You’ll touch domain/inventory, application/reservation-service, infrastructure/db/repositories, and api/controllers. The code is decoupled technically but cognitively glued together. You’re no longer thinking about “reserving inventory”, you’re thinking about “which layer handles this part of the transaction logic.”.

This approach optimizes for framework specialists. You have your database experts working in infrastructure/persistence, your API developers in presentation/controllers, and your domain modelers in domain/aggregates. It works beautifully in large enterprises where teams are organized by technical competency rather than business capability. But it creates friction when product teams want to ship vertical features quickly.

Vertical Heresy: Feature Sliced Design and Domain-First Thinking

Enter Feature Sliced Design (FSD), a methodology gaining traction particularly in frontend development but increasingly influencing backend architecture. FSD flips the organizing principle entirely: structure code around business functionality, not technical implementation. As the methodology states, you arrange your project “as users and business think about it, not as convenient for a specific framework.”.

FSD introduces a strict hierarchy of layers that dependencies must follow: appprocessespagesfeaturesentitiesshared. Each layer can only import from the same or lower layers, never from above. This isn’t just folder depth, it’s architectural law. The entities layer contains business entities (User, Product, Order) with their specific rules, while features encapsulate complete user behaviors like “add to cart” or “edit profile”, each containing their own UI, state management, and API integration.

The critical difference lies in the public API principle. In FSD, every slice exposes a strict contract through an index.ts file. Internal implementation details, whether that’s a React hook, a database repository, or a utility function, remain hidden. Other slices cannot import arbitrary files from peer directories, they must use the published API. This creates true modularity: you can refactor the internals of features/add-to-cart without breaking pages/product-details, provided you maintain the exported interface.

This approach aligns with Vertical Slices Architecture, where features are organized as self-contained pipelines from UI to database, minimizing cross-module coupling. The debate between this and traditional horizontal layering often devolves into semantics, but the practical impact on developer workflow is profound.

The Dependency Direction War

In horizontal architectures, dependencies flow inward. The domain layer knows nothing of infrastructure, infrastructure depends on domain interfaces. This protects business rules from technical implementation details. FSD maintains strict dependency rules too, but vertically: features can depend on entities, but entities cannot import from features. Violating this creates “architectural cycles” that sharply increase maintenance complexity.

Consider the inventory example. In a horizontal layout, you’d find inventory logic scattered across layers. In FSD, you’d have entities/inventory containing stock validation rules, and features/reserve-inventory containing the UI components, state management, and API calls needed to execute the reservation. The feature imports from the entity, not vice versa. If the reservation logic needs to check product availability, that rule lives in entities/product, imported by the feature.

This structure enforces the domain-first principle: identify your domains (Catalog, Cart, Order, User), map your entities within them, then build features as compositions of these entities. The alternative, starting with components, hooks, and services folders without domain context, leads to the dreaded “architecture by folder”, where structure exists but meaning doesn’t.

When Features Eat Your Architecture

FSD comes with its own failure modes. The methodology warns against “feature-giants”, monolithic features that accumulate unrelated functionality. A features/profile that handles password changes, avatar uploads, social account linking, and email verification is a trap. Instead, these should be separate features (features/change-password, features/upload-avatar) that can be composed on pages or used independently.

The pages layer in FSD has a specific mandate: they compose features and entities but contain no business logic. A pages/cart file imports entities/cart for data display, features/apply-promo-code for discount functionality, and features/checkout for the purchase button. The page doesn’t know how promo codes are validated or how checkout processes payments, it merely arranges these capabilities. This “pages assemble but don’t think” principle prevents business logic from leaking into view components.

Meanwhile, the shared layer acts as a utility belt for truly cross-cutting concerns, UI primitives like buttons and inputs, date formatting utilities, or base API clients. Crucially, shared must not know about domains. A shared/ui/ProductCard that imports entities/product violates the architecture. Instead, ProductCard belongs in entities/product/ui/, while shared/ui/Card remains a generic container.

The Team Scaling Factor

The directory debate ultimately reflects organizational structure. Horizontal layers optimize for teams divided by technical specialty: database engineers own infrastructure/persistence, frontend developers own presentation/components. This works in stable domains with clear technical boundaries.

Vertical slices optimize for product teams shipping business capabilities. When your team is organized around features (“the checkout team”, “the onboarding squad”), the code structure should mirror that. Each team owns vertical slices that span the full stack of their domain, reducing cross-team coordination costs.

The Reddit thread’s anxiety about “by the book” DDD reveals a deeper truth: Evans’ original patterns emerged from enterprise Java contexts in the early 2000s, before micro-frontends and feature teams became dominant. Modern distributed systems often require both approaches, horizontal layers for shared infrastructure (authentication, logging, persistence abstractions) and vertical slices for product features.

Concrete Takeaways for Practitioners

If you’re navigating this architectural schism, consider these heuristics:

Choose horizontal layers when building shared libraries, SDKs, or infrastructure platforms where technical consistency matters more than business features. Enforce the dependency direction with architectural fitness functions or linting tools like eslint-plugin-boundaries.

Choose vertical slices when building product applications with rapidly changing business requirements. Start with shared, entities, features, and pages. Introduce processes only when you have clear cross-page workflows (like multi-step checkout flows) that coordinate multiple features.

Never mix the import rules. If using FSD, enforce the public API principle strictly. Direct imports into internal slice directories (import { cartApi } from '@/features/add-to-cart/api/cartApi') create brittle dependencies that defeat the purpose of the architecture. Use barrel exports (index.ts) and lint against deep imports.

Evolve, don’t rewrite. Legacy codebases don’t need a “big bang” migration. Create a legacy/ directory for existing code and gradually extract entities and features into the new structure as you touch them. New code follows the new rules, old code stays quarantined until refactored.

The directory structure war won’t end with a victor. The most sophisticated codebases I’ve seen use horizontal layers for technical infrastructure and vertical slices for business capabilities. They recognize that domain/inventory and features/reserve-inventory aren’t mutually exclusive, they’re complementary views of the same system, each optimized for different types of change. The real skill isn’t picking a side, it’s knowing when to switch perspectives.

Share:

Related Articles