Eventos de dominio
Un agregado registra lo que le ha pasado. Otro código reacciona. La librería se apoya en @nestjs/cqrs para el transporte.
Aplicar
confirm(): void {
// ...comprobaciones y cambio de estado
this.apply(new OrderConfirmedEvent(this.id.getValue()));
}apply() no despacha nada. Añade el evento a una lista interna del agregado. En este punto no ha salido de la memoria.
Despachar
La línea que se olvida
await this.repository.save(order);
this.publisher.mergeObjectContext(order).commit(); // ← sin esto no pasa nadaSi falta, el comando se ejecuta con éxito, devuelve su respuesta, no lanza ningún error… y todos tus @EventsHandler se saltan en silencio. No hay aviso, no hay log, no hay nada que mirar. El correo de confirmación simplemente no se envía, y descubres por qué tres días después.
npx ddd validate tiene una regla para exactamente esto.
commit() es síncrono y sin espera: entrega los eventos al bus y vuelve. No devuelve una promesa que puedas esperar para saber que los handlers terminaron. Si necesitas esa garantía —en un test, por ejemplo— necesitas un mecanismo aparte; en ddd-es-lib el publicador expone un flush() justamente por eso.
El orden importa
await this.repository.save(order); // 1. persistir
this.publisher.mergeObjectContext(order).commit(); // 2. publicarPublicar antes de guardar abre una ventana en la que un handler lee un pedido que todavía no existe. Es una carrera que no se reproduce en local y sí en producción.
Reaccionar
@EventsHandler(OrderConfirmedEvent)
export class OrderConfirmedEventHandler implements IEventHandler<OrderConfirmedEvent> {
private readonly logger = new Logger(OrderConfirmedEventHandler.name);
async handle(event: OrderConfirmedEvent): Promise<void> {
try {
await this.mailer.sendConfirmation(event.orderId);
} catch (error) {
// La escritura ya ocurrió. El bug de un proyector no es problema de quien llamó.
this.logger.error(`No se pudo notificar ${event.orderId}`, error);
}
}
}Un manejador de eventos no debe tumbar el comando que lo produjo. El pedido está confirmado; que el correo falle no lo desconfirma. Traga el error, regístralo, y si hace falta reintenta por tu cuenta.
Sagas
Cuando un flujo abarca varios agregados —y por tanto varias fronteras de consistencia— la pieza que coordina es una saga:
@Injectable()
export class OrderSaga {
@Saga()
orderConfirmed = (events$: Observable<any>): Observable<ICommand> =>
events$.pipe(
ofType(OrderConfirmedEvent),
map((event) => new ReserveStockCommand(event.orderId)),
);
}Escucha el flujo de eventos y emite nuevos comandos. Es el sitio correcto para «cuando se confirma un pedido, reserva el stock», porque pedido e inventario son agregados distintos y no pueden compartir transacción.