3.8 KiB
8. Decisiones de diseño
Registro breve de las decisiones que más condicionan el código, con su motivo y su coste.
Arquitectura hexagonal con una librería Nx por capa
Las reglas de dependencia las comprueba el lint, no la disciplina del equipo. Coste: más librerías y más ficheros (un command y un handler por fichero, como en el patrón de referencia del equipo).
La misma estructura en API y web
libs/api/<contexto>/<capa> y libs/web/<contexto>/<capa>: quien conoce un lado se orienta en el otro.
Event sourcing en PostgreSQL (y no en MongoDB)
El requisito era escritura en PostgreSQL y lectura en MongoDB. El event store (events) implementa la misma API que
el MongoEventStore del patrón de referencia (persist, getEventsByStreamId). Diferencia: un conflicto de
versión lanza ConcurrencyError en lugar de solo registrarse, para no perder cambios en silencio.
commitAndWait()
El commit() de @nestjs/cqrs no devuelve la promesa del publisher: el command respondería OK aunque el event store
fallara. VersionedAggregateRoot.commitAndWait() espera la escritura y avanza la versión del agregado.
Redis entre escritura y lectura, y un bridge que espera
El requisito era que Redis llevase los datos de escritura a lectura. El EventsBridge hace el papel del change stream
del patrón de referencia, pero no usa eventBus.subject$.next(): así los handlers se ejecutarían en paralelo
(mergeMap), un IssueStatusChanged podría proyectarse antes que su IssueCreated y un fallo se perdería. El bridge
espera a cada @EventsHandler en orden y confirma en Redis (XACK) al final.
EventClsRegistry y @AutowiredEvent en el dominio
En el patrón de referencia están en infraestructura, pero los eventos de dominio los usan; ponerlos allí rompería la regla de capas.
Lookups fuera de los agregados
La unicidad de claves de proyecto, la numeración de tickets y los nombres de release no caben en un agregado. Se resuelven con tablas pequeñas en PostgreSQL. Coste: no forman parte de la misma transacción que los eventos (una clave reservada puede quedar huérfana si el guardado falla después).
Reglas críticas contra el event store, no contra Mongo
"No publicar una release con tickets sin terminar" se comprobaba primero contra el read model y fallaba si alguien metía un ticket y publicaba justo después. Ahora reconstruye esos tickets desde los eventos.
Proxy de tokens en la API
Pireon no permite CORS en /oauth/token y el client_secret no debe llegar al navegador.
POST /api/auth/token hace de intermediario, igual que la API de administración de la landing.
Caché de Apollo desactivada en la web
El estado vive en los stores de NgRx (alimentados por queries y subscriptions). Tener dos fuentes de verdad (Apollo cache y stores) solo añadiría inconsistencias.
Estado web con el plugin de eventos de NgRx Signals
Los efectos se declaran con withEventHandlers (nombre en v22 de lo que antes era withEffects), y lo que emiten se
despacha automáticamente.
Facades delante de los stores
Las páginas usan una facade por store (BoardFacade, IssueFacade…) y no conocen ni los stores ni los eventos, que
ya no se exportan. La facade expone el estado como señales y las intenciones como métodos, y pone el contexto (id del
proyecto o del ticket abierto). Así se puede cambiar cómo se gestiona el estado sin tocar las páginas, y la lógica de
presentación se prueba sin plantillas. Coste: una clase más por store, casi toda de reenvío.
Imagen de la API con dependencias propias
El package.json raíz incluye Angular y @pi/ui (del Nexus privado). La imagen de la API usa
apps/api/package.json, así que ni lleva el frontend ni necesita el token del Nexus en ejecución.