3.3 KiB
3.3 KiB
F-153 — Implementer Evidence
Build
Feature: F-153 — Orders: associate the linked customer's email to the order read model and display it (customer email was missing despite the user having an email).
Changes (production)
src/modules/orders/domain/order.ts— addedemail?: string | nulltoOrderView(read model).Ordercore aggregate is untouched (email is a denormalized read attr).src/modules/orders/infrastructure/pg-order-repository.ts:- added
email: string | nulltoOrderRow; - added
toOrderView(row, items)helper; findById,findByIdAndUserId,findAll,searchnowSELECT o.*, u.emailwithLEFT JOIN identity_users u ON u.id = o.user_id(reuses the existingsearchjoin pattern);- every
OrderViewis built viatoOrderView, soemailis always populated (string | null;nullwhen the order has no linked identity_user).
- added
src/modules/orders/api/orders.routes.ts:serializeOrdernow emitsemail: order.email ?? null(detail + list);- admin force-transition (
POST /orders/:id/transitions/admin) consumesorder.email(the associated email) and no longer issues a separate inlineSELECT email FROM identity_userslookup → no N+1, single source of truth.
Changes (tests)
- new
src/modules/orders/infrastructure/pg-order-repository.test.ts:findByIdresolvesemailvia theidentity_usersjoin (asserts the query containsidentity_users+u.emailand returns the row's email);- returns
nullwhen the order has no linked identity_user; - returns
undefinedwhen the order does not exist.
- existing
OrderViewliterals (order-service,payments-service,checkout-service) needed no change (emailis optional on the view; fixtures omit it → serialized asnull).
Boundary / compatibility notes
identity_usersis referenced only as a SQL table name (pre-existing pattern insearch); no TypeScript import crosses the identity↔orders boundary →lint:boundariesunchanged.- No migration (
identity_users.emailalreadycitext NOT NULL UNIQUE). - Additive API field; no state-machine / endpoint / contract change.
Evidence (commands + results)
npx tsc --noEmit→ 0 errors (TSC_EXIT:0).npx vitest run→ 200 passed | 56 skipped (50 files); new F-153 suiteorders/infrastructure/pg-order-repository.test.ts→ 3 passed.npx eslint(touched files) → 0 errors / 0 warnings (ESLINT_EXIT:0).npx prettier --checkon touched files → only pre-existing baseline warnings (identical toHEADfor bothpg-order-repository.tsandorders.routes.ts; my added lines are short / long string literals accepted by Prettier; no new reformatting).npm run lint:boundaries→ 1 violation, pre-existing (security/routesR1); 0 new violations from F-153.git diff --check→ clean (no whitespace errors)../scripts/verify.sh→ exit 0 (backlog 270 features valid; runtime-status advance to build/running with F-153 in_progress — see below).
Decisions
- Resolved via the read model (Option A in ADR) rather than route-layer lookups (Option B)
because
ordersalready joinsidentity_usersin SQL forsearch; generalizing that join to all order reads gives the email as a first-class read attribute and removes the per-request lookup from the admin transition.