Files
mercadodevida/work/artifacts/F-153/architect.md
2026-08-22 07:41:08 +02:00

52 lines
3.0 KiB
Markdown

# 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.