Skip to content

ADR 029 Layer-first Context Feature Slices

Feature-first placement makes the feature folder look like the highest architecture authority. It also obscures dependency direction, encourages feature-to-feature deep imports, and can turn CQRS into a false global layer.

The workspace owner explicitly selected DDD layers as the top-level authority on 2026-07-13. Bounded context and feature slice remain important, but they are nested inside each layer.

Backend production code follows this order:

src/sanmopia_modernization/
├── domain/<bounded_context>/features/<feature>/
├── application/<bounded_context>/features/<feature>/
├── adapters/<bounded_context>/features/<feature>/
├── interfaces/<bounded_context>/features/<feature>/
├── orchestration/<bounded_context>/features/<feature>/
└── platform/

Thin transport hosts such as interfaces/mcp may exist, but they only bind protocol concerns to context-owned interface slices. They do not own business use cases.

The dependency direction is fixed:

domain <- application <- adapters/interfaces <- orchestration
  • domain owns business vocabulary, facts, invariants, policies, and ports whose ownership is genuinely domain-level.
  • application coordinates use cases and declares consumer-owned ports and public feature contracts.
  • adapters implement ports. They do not construct or import interface APIs.
  • interfaces authenticate, authorize, validate transport contracts, and call application entry points. They do not import adapter implementations.
  • orchestration is the composition boundary for adapters, interfaces, workflows, and process startup.
  • platform contains generic runtime, configuration, observability, and provider facilities. It owns no business rule and is never imported by domain or application.

Sibling features and bounded contexts cannot deep-import each other’s implementation modules. Collaboration requires an explicitly owned public contract or port. A direct import is not accepted merely because Tach currently permits it.

CQRS is not a top-level layer and is not mandatory. It is optional structure inside an application feature:

application/
└── knowledge/
└── features/
└── report_catalog/
├── commands/
│ └── publish_report.py
└── queries/
└── search_reports.py
  • A read-only feature has queries/ only.
  • A write-only feature has commands/ only.
  • A mixed feature has both only when separate models clarify the use case.
  • Empty commands/ or queries/ directories are forbidden.
  • A cqrs/ wrapper directory is forbidden.
  • Global application/commands and application/queries directories are forbidden.
  • Domain aggregates and policies remain in the domain feature; executable use cases and read projections belong to the application feature.

Architecture tests and Tach declarations must converge on this decision. A green gate is insufficient while production modules remain outside its graph. The target gates must detect:

  • reverse layer imports;
  • adapters <-> interfaces imports;
  • context or feature deep imports without a public contract/port;
  • flat business interface modules outside context/feature slices;
  • business composition in adapters or interfaces;
  • root runtime/composition files outside orchestration;
  • obsolete cqrs/ wrappers, global CQRS directories, and empty command/query directories.

The 2026-07-13 audit confirmed that domain/application/adapters roots already exist and no top-level feature root exists. Domain framework purity and the currently declared Tach boundaries pass.

Backend commit ee7b9b8 closed the first enforceable slice:

  • moved reservation-booking API composition from adapters to orchestration/reservation_operations/features/reservation_booking;
  • added structural layer-direction tests and Tach root prohibitions for adapters, interfaces, and orchestration;
  • moved settlement commands and queries out of cqrs/ into populated, use-case-named application subpackages;
  • moved the flat document code predicate into the public document_identity domain feature contract.

Backend 8aeaf0a then replaced four representative sibling-feature deep imports with public domain, application, and adapter feature contracts. This is a bounded first slice, not proof that every sibling import is compliant.

Backend 4e87532 moved the reservation-booking application slice to application/reservation_operations/features/reservation_booking and kept the authenticated mother submitter distinct from the operational branch actor.

Backend b760b05 removed the flat adapters/reservation_booking package. Booking adapters now live under adapters/reservation_operations/features/reservation_booking, mother context has its own feature, and daily-report, service-calendar, settlement, and caregiver-payout consumers live with their owning context or orchestration slice. A structural architecture test forbids legacy Python sources and verifies these migrated adapter slices. Feature-level Tach modules pass exact, interface, dependency, and external checks.

Backend a768cc2 removed the flat reservation-booking API, HTTP, and Restate interface modules. They now live under interfaces/reservation_operations/features/reservation_booking and expose production contracts only through the feature package. A structural test rejects legacy flat files and production deep imports. The moved tests remain colocated, shared HTTP fixtures use an explicit test-support module, and Tach passes with a feature-level interface declaration.

Backend 438e8dc moved the branch-office profile API, HTTP boundary, and colocated tests into interfaces/branch_operations/features/office_profile. Production composition imports only the public feature package, the architecture gate rejects the old flat modules and deep production imports, and Tach owns the new feature module.

The same verification run exposed two pre-existing branch settlement export tests whose fixtures omitted mandatory dataset revision lineage. Backend f0f32bb restored those fixture facts without weakening the production model. The combined diff passed 528 Tach-selected tests, all exact/interface/dependency/ external checks, and a confidence-100 Vulture scan with zero candidates.

Backend 52def80 then moved the branch service-area policy API, HTTP boundary, and colocated tests into interfaces/branch_operations/features/service_area_policy. Route and contract behavior stayed unchanged. The public feature package, flat-module and deep-import guards, and Tach feature declaration passed 514 affected tests; Tach reported zero diagnostics and the focused confidence-100 Vulture scan reported zero candidates.

Backend c9b06d7 adds a new read-only reservation-operation worklist as an ADR-029 exemplar:

  • the use case exists only under application/reservation_operations/features/reservation_operation_worklist/queries; it has no empty or unnecessary commands/ package;
  • Supabase and SpiceDB implementations live under the matching adapter feature;
  • FastAPI/API models and colocated tests live under the matching interface feature;
  • production composition imports the public feature packages; and
  • Tach declares separate application, adapter, and interface modules with inward-only dependencies and explicit adapter/interface prohibitions.

The current Tach inventory has 178 modules. Exact dependency/interface and external checks reported zero diagnostics, Tach selected 33 affected tests and skipped 4,921 unaffected tests, and the focused confidence-100 Vulture MCP scan reported zero candidates. The stage-data browser proof belongs to the feature scenario, not to this architecture decision; it does not upgrade ADR-029 from partial while the root composition and flat interface gaps below remain.

Implementation is still partial because:

  • interfaces remains largely flat outside the reservation-booking pilot;
  • top-level orchestration now has one pilot slice, while platform placement is incomplete;
  • the runtime composition root remains in a large top-level main.py;
  • some application/adapter contexts do not yet use features/<feature>;
  • current Tach declarations do not cover the full production graph or all feature-level contract rules.
  1. Expand the initial architecture tests and Tach root rules to the complete production graph and public feature contracts.
  2. Replace the remaining flat reservation authorization helper with an application permission port and context-owned interface adapters without assigning shared authorization to the booking feature.
  3. Extract public contracts/ports for verified cross-feature collaborations.
  4. Move flat interfaces and root runtime composition incrementally, with colocated tests and stage smoke proof per slice.

Architecture changes must start from layer ownership, then context, then feature. New code cannot copy a known partial path as precedent. Existing paths remain migration debt until code movement, import gates, tests, and runtime proof all agree.

  • 2026-07-13: accepted from the workspace owner’s explicit architecture direction; supersedes ADR 002. Implementation remains partial pending full graph enforcement and incremental migration.
  • 2026-07-13: backend ee7b9b8 added the first layer-direction gate, moved reservation API composition to orchestration, removed the settlement cqrs/ wrapper, and promoted a flat domain helper into a public feature contract.
  • 2026-07-13: backend 8aeaf0a published representative cross-feature contracts for payment purpose, payment/compensation projection inputs, service-catalog storage facts, and service-balance closeout records.
  • 2026-07-13: backend 4e87532 moved the reservation-booking application slice into the reservation-operations context and separated authenticated submitter evidence from the branch operational actor.
  • 2026-07-13: backend b760b05 removed the flat reservation-booking adapter, distributed cross-feature consumers to their owners, and added migrated adapter structure plus feature-level Tach enforcement.
  • 2026-07-13: backend a768cc2 moved reservation-booking API, HTTP, Restate, tests, and test support into the reservation-operations interface slice and enforced public production imports.
  • 2026-07-13: backend 438e8dc moved branch-office profile API, HTTP, and tests into the branch-operations interface slice; f0f32bb repaired stale export test lineage found by the affected-test gate.
  • 2026-07-13: backend 52def80 moved branch service-area API, HTTP, and tests into the branch-operations interface slice without changing behavior.
  • 2026-07-13: backend c9b06d7 added the query-only reservation-operation worklist across application, adapter, and interface feature slices and passed Tach exact/external, 33 affected tests, and focused Vulture with zero findings.