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

3.0 KiB

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.