Microservices Decomposition

1. Purpose

The decomposition follows the bounded contexts in the domain model. It maps each bounded context to a service. The services are independent deployables from day one (ADR-007); their technology is recorded in the technology stack.

2. Domain-to-service map

flowchart LR PP["Product Passport"] --> PPS["passport-service"] PV["Personalization & Verdict ★"] --> PVS["personalization-service"] FR["Fridge"] --> FRS["fridge-service"] ID["Identity & Consent"] --> IDS["identity-service"] BA["Brand Analytics"] --> BAS["brand-analytics-service"] EDGE["Public edge"] --> GW["api-gateway"] PRIV["Privacy aggregation"] --> MP["measurement-pipeline"] classDef core fill:#fff3bf,stroke:#e8a500,stroke-width:3px,color:#000 class PV,PVS core

The gateway and measurement pipeline are architectural components, not bounded contexts. The other services map directly to one bounded context.

3. Mapping

Domain boundary Target service Owns
Product Passport passport-service Resolver, Product Catalog, ScanRecords, Resolved Passport assembly and source adapters
Personalization & Verdict ★ personalization-service HealthProfile, VerdictService, screening policies and personalized Alerts
Fridge fridge-service Fridge aggregate, FridgeItems, freshness and waste projections
Identity & Consent identity-service VisitorIdentity, Account, consent ledger, Brand and BrandUser
Brand Analytics brand-analytics-service Tenant-scoped aggregate read models and dashboard queries
Public edge (not a bounded context) api-gateway Routing and access to the services
Privacy pipeline (not a bounded context) measurement-pipeline Raw consumer-side measurement facts and minimum-group-size aggregation

Verdict remains inside personalization-service because it is part of the Personalization & Verdict bounded context.

The measurement pipeline remains separate from brand-analytics-service because raw consumer facts must be aggregated before crossing the privacy wall.

4. Data ownership

Each extracted service owns its data. Other services use IDs, APIs or events; they do not read another service's tables.

Owner Private data store Important records
passport-service Passport database ProductCatalogEntry, ScanRecord, source cache
personalization-service Encrypted personalization database HealthProfile, rule sets
fridge-service Fridge event store + projections Fridge event streams, freshness and waste projections
identity-service Identity database / managed auth integration VisitorIdentity, Account, consent ledger, Brand, BrandUser
measurement-pipeline Short-retention consumer-side measurement store Raw measurement facts and aggregation windows
brand-analytics-service Analytics read store Minimum-group-size, tenant-scoped metric batches

A single PostgreSQL instance hosts these logical stores as one schema per service, but ownership remains exclusive: each service connects with its own database role, GRANT-restricted to its schema, so isolation is enforced by the database rather than by convention (ADR-009).

5. Shared tenant seed (development)

The Brand aggregate lives in the identity schema; brand-owned records elsewhere reference it by id only, since cross-schema constraints are impossible by design (ADR-009). Development seeds therefore share one fixed list of demo-Brand UUIDs — any seed that attributes data to a Brand must use these:

Brand UUID
Bergbauer b0000000-0000-4000-8000-000000000001
NaturPur b0000000-0000-4000-8000-000000000002
HavreGård b0000000-0000-4000-8000-000000000003
Delizia b0000000-0000-4000-8000-000000000004

Used today by services/passport-service/migrations/0002_seed_catalog.sql (13 products, including the web app's three mock GTINs) and by the identity-schema brands seed in services/identity-service/src/adapters/postgres/migrations/0004_seed_brands.ts (the two seeds must agree by convention, since ADR-009 rules out a cross-schema FK).