ADR-0025 — Federated multi-location
- Status: Accepted
- Date: 2026-05-11
- Decision-makers: Tom Anderson
Context
Multi-location capability is on the roadmap as Stage 7. There are two ways to support a bike-shop chain with multiple physical locations:
Option 1 — Federated. One Helm deployment per location. Each has its own Worker, its own D1, its own R2. A small aggregation layer (in code or in a separate Worker) handles chain-level queries by fanning out across member deployments and merging results. Inventory transfers between locations are a special transaction type that posts on both ends.
Option 2 — True multi-location. One Helm deployment serves multiple locations. A location_id column is added to every relevant table (transactions, inventory, tickets, staff). Queries are scoped by location; chain queries aggregate within the single database.
The choice has architectural and product implications:
- Single-tenant property. ADR-0003 commits to one D1 per shop, with isolation enforced at the deployment layer (no
tenant_idcolumns). True multi-location contradicts this: a chain shares a D1 across locations, so isolation goes back to being an application-layer property gated bylocation_id. - Trust pitch. "Your data lives in its own database that no other shop's code can reach" gets weaker when the database is shared across locations. Owners often want the locations to feel like one business; outside observers (insurance, lawyers, ex-employees) often need them to feel like separate businesses.
- Engineering cost. True multi-location requires adding
location_idto most tables and scoping every query. Federated requires a small fan-out layer. - Scale. Federated performs fine for small chains (2–10 locations). At very large scale (50+ locations under one owner), federated fan-out queries get expensive and a true-multi-location model would be faster.
The realistic Helm scale for the foreseeable future is small chains: a 2-location Specialized shop, a 3-location Trek Bicycle Store, a small franchise. Federated handles this well. The cost of building true multi-location now — and contradicting ADR-0003 — would be paid for a future that may never arrive.
Decision
Federated multi-location for v1+. Each location is its own Helm deployment with its own D1 / R2 / Worker. The existing schema does not change. Chain capability is added through:
- A
chain_membershipsrecord linking locations into a chain. Lives either in a small aggregation service or replicated to each member's D1 — engineer to refine when the slice is specified. - A federated reporting layer that fans out queries across member deployments and aggregates results. Per-chain dashboards (revenue by location, inventory by location, top mechanic across the chain) live in this layer.
- A chain owner dashboard UI that wraps the per-location dashboards and surfaces aggregate views. The operational unit is the chain even when the technical unit is the location.
- Inventory transfers as a defined cross-location transaction type. Posts as an outbound transfer on the sending location and an inbound transfer on the receiving location. Cost basis flows with the inventory.
- Customer record sharing as opt-in only. A customer who shops at multiple locations of the same chain can be linked by separate consent, not by default. Per-location records remain primary; the link is reference, not merge.
- Staff who work across locations clock in/out per location (slice 19). Same staff record can have authorization for multiple locations' deployments via the existing per-staff permission overrides (ADR-0014).
True multi-location remains a possible future migration if a customer's scale demands it. The migration would be substantial (consolidating N D1s into one, adding location_id columns, rewriting queries). The federated model is designed so it doesn't paint into a corner.
Consequences
Positive:
- No changes to the existing schema. The 11 slices already in flight don't need rework.
- Single-tenant per shop (ADR-0003) is preserved. Each location's data is cleanly separable. Compliance, exports, and the data-ownership commitment all apply per location.
- Smaller blast radius for any single deployment's bugs or outages. A bug at location A doesn't affect location B.
- The "this location is its own business" reading is correct by construction. Useful for shops where the locations are legally separate entities (franchises, separate corporations under one chain brand).
- Deploy cost scales linearly with locations; no surprise nonlinear costs from cross-tenant query patterns.
Negative:
- Cross-location queries are slower than they would be in a true multi-location model. The fan-out latency is the cost.
- Inventory transfers require coordination across two deployments. A network blip during the transfer needs idempotency-key reconciliation.
- A chain owner sees N deployments to manage, not one. Onboarding a new location is N+1 work, not "add a row."
- Schema migrations run N times per chain, not once. Already true per single-tenant; chains amplify it.
Mitigations:
- Most early chains are small enough that fan-out performance is invisible. The federated layer caches aggregate results where appropriate.
- Inventory transfer is a defined workflow with idempotency keys (ADR-0015) on both ends. Reconciliation is automatic.
- The chain owner dashboard abstracts the N-deployments reality. The owner sees one UI; the platform handles the fan-out.
- Deploy automation for a new location is
scripts/onboard-shop.sh(already needed for the single-shop case) plus achain_membershipsrow. Marginal cost is small.
Notes
This ADR doesn't change ADR-0003; it explains how chain capability is achieved without violating it. The two ADRs are complementary, not in tension.
If a future customer with 50+ locations forces a true-multi-location migration, the federated model's per-location D1s become the input to the consolidation (the data is already cleanly separable). It's a forward-compatible position.
See also
- Roadmap — Stage 7
- ADR-0003: Single-tenant architecture — the property this preserves
- Single-tenant per shop — the principle behind ADR-0003
- ADR-0015: Idempotency keys on external writes — applies to inventory transfers between locations
- Deployment topology — the per-shop deployment shape this builds on
- Data ownership — preserved per location