# F-153 — Architectural Decision Record ## Status: accepted (architect) ## Context The order read model (`OrderView`) carries the customer's `userId` but never the `email`, even though `orders_orders.user_id` references `identity_users` whose `email citext NOT NULL UNIQUE` always exists. As a result: - Order serialization (`serializeOrder`) never displays the customer email — the reported "customer email missing" symptom. - The admin force-transition (`POST /orders/:id/transitions/admin`) worked around the gap with an inline `SELECT email FROM identity_users WHERE id = order.userId`, a fragile per-request lookup that produced "El cliente no tiene email asociado" whenever the view itself did not carry the association (and would N+1 if reused broadly). ## Decision Associate the customer's email to the order **read model** and resolve it once, in the orders repository, by joining `identity_users` (the same `LEFT JOIN` already used by the `search` method): 1. Add `email: string | null` to `OrderView` (`identity/domain` vs `orders` boundary untouched; `Order` core aggregate stays without email — email is a denormalized read attribute). 2. Add `email` to `OrderRow`; extend `findById`, `findByIdAndUserId`, `findAll`, and `search` to `SELECT o.*, u.email FROM orders_orders o LEFT JOIN identity_users u ON u.id = o.user_id`, and build the view through a single `toOrderView(row, items)` helper. 3. Surface `email` in `serializeOrder` (detail + list). 4. In the admin force-transition, consume `order.email` as the single source of truth and remove the inline lookup. ## Rationale - **Single source of truth**: once the email is on the view, both the API serialization and the admin notification read it from one place — no divergent lookups. - **Reuses an established pattern**: `orders/infrastructure/pg-order-repository.ts` `search` already joins `identity_users`; F-153 generalizes that to all order reads. No new pattern. - **Boundary-safe**: `identity_users` is referenced only as a SQL table-name string at the orders infrastructure layer (no TypeScript import of `identity` from `orders`). `identity` does not import `orders`/`payments`. `lint:boundaries` R1/R2 unaffected. - **No migration / backward compatible**: `identity_users.email` already exists and is `NOT NULL`; `email: string | null` on the view is null only when an order has no linked user. Serialization only adds a field. - **No N+1 on lists**: `findAll`/`search` resolve email in the same SELECT, so the admin list returns emails without per-item lookups. ## Consequences - Additive API field `email` on order DTOs; frontend/admin consumers gain the value for free. - `order-service.test.ts`, `payments-service.test.ts`, `checkout-service.test.ts` `OrderView` literals gain `email: null`. - New `orders/infrastructure/pg-order-repository.test.ts` covers email resolution (happy path and no-linked-user). - Admin transition no longer depends on a raw `identity_users` query in the route layer.