Vertical Slice Architecture (VSA) raises recurring questions about ownership, shared data, and external dependencies. This deep-dive clarifies the vocabulary confusion between slices, modules, and bounded contexts, arguing that most application areas are slices or modules within a single context — not autonomous bounded contexts. The core pattern: each slice declares narrow function types for its dependencies (in its own vocabulary), and a single composition root wires everything together. This eliminates cycles between modules, enables easy testing without mocking frameworks, and keeps business logic per entity, read models per query, and database schemas per module. The approach also applies to frontend composition, where pages or backend-for-frontend slices stitch data from multiple modules. Concrete TypeScript examples illustrate handler dependency injection, structural typing, and stub-based testing.
Table of contents
Slice, module, contextSlices boundaries in practiceDuck typing and dependenciesEverything outside the slice is externalTwo entry points, one featureVertical Slices and DatabaseVertical Slices and FrontendTLDRQuestions this post answers
In vertical slice architecture, how should one slice access data owned by another slice without creating tight coupling?
Each slice declares a narrow function type for the data it needs, in its own vocabulary, rather than importing the other slice's interface directly. A composition root near the entry point wires the real implementation to that declared type. This means neither slice imports the other — the adapter lives in one file, cycles are avoided, and each slice remains independently testable by passing stubs that satisfy the declared function type. Teams navigating VSA boundaries track patterns like this on daily.dev as their codebases grow.
How do I avoid circular module dependencies in vertical slice or modular architecture without extracting everything into a shared common module?
Declare narrow function types at the consumer side rather than importing from the provider module. For example, an Orders module declares CheckContractor and a Pricing module declares GetCompletedOrders — neither imports the other. The composition root supplies adapter functions that query one module and map results to the shape the other declared. This defers ownership decisions and keeps adapters small and localized, avoiding the 'common module becomes a dumping ground' problem. Developers resolving module boundary disputes find concrete patterns like this on daily.dev.
Should database tables be split per vertical slice in vertical slice architecture?
No — database schemas should be per module, not per slice. Business logic belongs per entity or aggregate, read models are built per query (a table per screen or report is appropriate here), and schemas are scoped to modules like Orders or Pricing. A schema per slice produces migrations that map to nothing and requires joins across many schemas to render a single screen. Architects deciding on persistence boundaries for modular systems find grounded guidance on daily.dev.