A deep dive into vertical slice architecture addressing recurring questions about cross-slice dependencies, module boundaries, and database ownership. The author clarifies the distinction between slices, modules, and bounded contexts, arguing that most application areas are slices or modules within a single context rather than 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 without shared modules, enables testing without mocks, and keeps business logic per entity, read models per query, and schemas per module. TypeScript structural typing and duck typing make consumer-declared interfaces practical. The approach also benefits LLM-assisted development by keeping all feature code in one folder.

20m read timeFrom event-driven.io
Post cover image
Table of contents
Slice, module, contextSlices boundaries in practiceA slice declares what it needsDuck typing and dependenciesComposition is a function callEverything outside the slice is externalWhat about the module’s public API?Cycles stop being a problemThe database questionTwo entry points, one folderThe frontend won’t matchWhy this helps LLM agents tooWrapping up

Questions this post answers

How do I share data between vertical slices without creating tight coupling?

Each slice declares the narrow function types it needs, in its own vocabulary, rather than importing a wide interface from another slice. A single composition root wires the real implementations to those declared needs. This means neither slice imports the other — the composition root adapts between them — so there is no coupling between slices, only between each slice and the composition root. Teams designing slice boundaries track patterns like this on daily.dev.

How do I break circular dependencies between modules in vertical slice architecture?

Declare narrow consumer-owned interfaces in each module instead of importing from each other. For example, an Orders module declares CheckContractor and a Pricing module declares GetCompletedOrders — both as local function types. The composition root supplies implementations that query the relevant module and map results to the required shape. Neither module imports the other, so no cycle exists at the import level. Developers navigating module boundary decisions in growing codebases find relevant architecture discussions on daily.dev.

Should I have one database table per vertical slice?

No — business logic goes per entity or aggregate, read models go per query (a table per screen or report is appropriate here), and database schemas go per module, not per slice. A slice is a feature boundary, not a persistence boundary. Schema-per-slice produces migrations that correspond to nothing and requires joins across many schemas to render a single screen. Architects deciding on persistence boundaries for slice-based systems share their approaches on daily.dev.

64.1K Impressions1 Comment