Episodio 2

Comandos vs eventos

lectura · 13 mindominio · BCPS Bankoperación · transferencia interna

La primera pregunta de Fidelización

En el episodio anterior, el equipo de Fidelización se suscribió a TransferenciaCompletada sin pedir permiso a nadie. Una semana después llega su primera pregunta: «Nos llega el evento, pero solo trae un id. ¿Cuánto era? ¿Quién la hizo?».

Alguien de Cuentasver en el mapa propone un atajo: «Nosotros ya publicamos MovimientoRegistrado con el importe y el titular. Escuchad ese y listo».

Son dos preguntas distintas, y las dos salen de algo que en el episodio 1 pasamos por alto. En la simulación viajaban dos tipos de mensaje: cuentas.moverDinero, que pide algo a un servicio concreto, y TransferenciaCompletada, que cuenta algo que ya ha pasado. Uno es un comando y el otro un evento. Parecen lo mismo con otro nombre. No lo son, y de esa diferencia depende qué debe llevar dentro un evento y cuál hay que escuchar.

El mismo evento, con y sin datos

Los dos paneles hacen la misma transferencia. Transferencias manda el comando MoverDinero a Cuentas y luego publica TransferenciaCompletada. La diferencia está en qué lleva el evento: a la izquierda, solo el id; a la derecha, lo que necesitan los que lo reciben. Las flechas naranjas son consultas que los consumidores hacen de vuelta. Tira la API de consulta de Transferencias, cambia el evento que escucha Fidelización y prueba a enviar más dinero del que tiene Ana.

Qué ha pasado

Pedir no es lo mismo que contar

Un comando pide algo: «mueve 100 € de Ana a Luis». Tiene un destinatario concreto, que es quien sabe hacerlo, y ese destinatario puede negarse. Quien lo envía quiere saber qué ha pasado, así que espera una respuesta (ahora o más tarde).

Un evento cuenta algo: «la transferencia T-7 se ha completado». Ya ha pasado, así que no hay nada que aceptar o rechazar. Quien lo publica no sabe quién lo escucha ni le importa: cada receptor decide qué hacer con él.

ComandoEvento
Se nombraEn imperativo: MoverDineroEn pasado: TransferenciaCompletada
DestinatariosUno, que el emisor conoceCero, uno o muchos, que el emisor no conoce
¿Se puede rechazar?SíNo: ya ha pasado
¿Quién decide qué se hace?El emisorCada receptor
¿Quién es dueño del mensaje?El receptor (Cuentas define MoverDinero)El emisor (Transferencias define TransferenciaCompletada)

Fíjate en que la diferencia no es de transporte. Un comando puede viajar por una llamada REST o por una cola; un evento, casi siempre por un broker. Lo que cambia es la intención: quién decide y quién depende de quién.

El comando puede decir que no

Con 2.000 €, Cuentas rechaza MoverDinero: Ana solo tiene 1.000.probar en el laboratorio Es exactamente lo que se espera de un comando. Cuentas es quien sabe si hay saldo, así que es quien decide, y Transferencias tiene que enterarse antes de seguir.

Después, Transferencias publica TransferenciaRechazada. También es un evento, y tampoco se puede rechazar: la transferencia se ha rechazado, eso ya es un hecho. Notificaciones avisa a Ana; a Fidelización no le interesa.

Un olor típico: un «evento» que en realidad es una orden. EnviarSMSAAna publicado en un broker, o TransferenciaCompletadaParaFidelización. Si el nombre dice qué tiene que hacer el receptor, o para quién es, el emisor está decidiendo por los demás. Es un comando disfrazado, con los inconvenientes de los dos: el acoplamiento de un comando y la falta de respuesta de un evento.

¿Qué lleva dentro el evento?

En el episodio 1, TransferenciaCompletada solo llevaba el id. Es la opción más prudente: no comprometes datos que quizá cambien, y no sabes qué necesitará cada consumidor. Pero mira lo que pasa en el panel de la izquierda: cada consumidor recibe el evento y lo primero que hace es preguntar a Transferencias por el resto. Tres consumidores, tres consultas por cada transferencia.

Tira la API de consulta de Transferencias y envía otra.probar en el laboratorio Con el evento mínimo, los tres consumidores se quedan atascados: tienen el evento, pero no pueden hacer nada con él. El broker los había desacoplado de Transferencias y la consulta de vuelta los ha vuelto a acoplar. Es el acoplamiento temporal del episodio 1, que entra por la puerta de atrás.

Esto tiene nombre. Martin Fowler distingue varios tipos de evento según cuánto llevan dentro:

TipoQué llevaQuién pregunta después
Notificación (evento mínimo)Qué ha pasado y el idTodos los que necesiten algo más
Transferencia de estado (event-carried state transfer)Los datos que necesitan los consumidoresNadie: cada consumidor se guarda lo que le interesa
Event sourcingCada cambio, con todo el detalle; la secuencia de eventos es el estadoNadie: el estado se reconstruye leyendo los eventos

El tercero ya lo conoces aunque no lo parezca. El libro de movimientos de Cuentas funciona así: el saldo no se guarda, se calcula sumando los movimientos, y un error no se borra sino que se corrige con otro movimiento. Es una forma de guardar datos más que de comunicar servicios, y tiene su propio tema en el catálogo.

Lo que cuesta un evento con datos

Si el evento con datos gana en la simulación, ¿por qué no meterlo todo siempre? Porque cada campo tiene un precio:

¿Y el teléfono de Ana? Notificaciones puede mantener su propia copia de los datos de contacto escuchando DatosClienteActualizados, que publica Clientesver en el mapa. Eso también es transferencia de estado: en lugar de preguntar a Clientes en cada aviso, Notificaciones se entera cuando algo cambia.

Elige el evento por lo que significa

Queda la propuesta de Cuentas: escuchar MovimientoRegistrado, que ya trae importe y titular. Cambia el evento que escucha Fidelización y envía una transferencia.probar en el laboratorio Luis acaba de ganar puntos por recibir dinero.

No es un fallo de datos, y por eso pasa igual en los dos paneles. Una transferencia interna genera dos movimientos: el cargo de Ana y el abono de Luis. MovimientoRegistrado cuenta que Cuentas ha anotado algo en un libro; TransferenciaCompletada cuenta que un cliente ha hecho una operación. Los puntos son por lo segundo. Si Fidelización escucha lo primero, tiene que adivinar qué movimientos son transferencias, cuáles son el lado del ordenante, cuáles son una nómina o una devolución… y acabará reconstruyendo, mal, lo que Transferencias ya sabe.

Dicho en términos de DDD: cada evento pertenece al contexto que lo publica y habla su idioma. «Movimiento» es vocabulario de Cuentas; «transferencia», de Transferencias. Escucha el evento del contexto que entiende de lo que te interesa.

Criterio

Tres decisiones, en este orden. Primero, ¿comando o evento?

  1. ¿Quieres que un servicio concreto haga algo, y necesitas saber si lo ha hecho?Transferencias necesita que Cuentas mueva el dinero, y saber si ha podido.
    comando
  2. ¿Estás contando algo que ya ha pasado en tu contexto?La transferencia se ha completado; se ha rechazado; el cliente ha cambiado de teléfono.
    evento
  3. Probablemente es una consulta («¿cuál es el saldo?»): una llamada normal, ni comando ni evento.

Segundo, ¿qué lleva el evento?

Mejor mínimo si…Mejor con datos si…
Los datos son sensibles o pesados (un documento, datos personales).Los consumidores necesitan siempre los mismos pocos datos.
Los consumidores necesitan el estado más reciente, no una foto.Lo que cuenta el evento no cambia después (un importe, una fecha).
Hay pocos consumidores y pocos eventos: las consultas no pesan.Hay muchos consumidores o mucho volumen: las consultas se multiplican.
Todavía no sabes qué necesitará cada consumidor.No quieres que tus consumidores dependan de que tú estés en pie.

Un término medio razonable: los datos que definen el hecho (quién, cuánto, cuándo) van dentro; los que describen a otras entidades (el teléfono de Ana, la dirección de Luis) se quedan en su contexto.

Tercero, ¿qué evento escuchas? El que publica el contexto responsable de lo que te interesa, aunque otro traiga los datos más a mano.

Mensaje en BCPS BankTipoPor qué
MoverDinerocomandoTransferencias pide algo a Cuentas, que puede negarse.
EvaluarRiesgocomando (o consulta)Transferencias necesita la respuesta de Antifraude para decidir.
TransferenciaCompletadaeventoCuenta un hecho de negocio. Lo escuchan Notificaciones, Reporting y Fidelización.
MovimientoRegistradoeventoCuenta un hecho del libro de Cuentas. Lo escuchan Reporting y Antifraude, no Fidelización.
DatosClienteActualizadosevento con datosLos contextos que necesitan el contacto del cliente se guardan su copia.

En el mundo real

Los tipos de evento de este episodio vienen de un artículo de Martin Fowler de 2017, What do you mean by “Event-Driven”?, escrito precisamente porque los equipos decían «eventos» y cada uno entendía una cosa. Merece la pena leerlo entero.

Algunas herramientas separan comandos y eventos en su propia API. En NServiceBus (.NET), un comando se envía con Send a un único destino y un evento se publica con Publish, y la herramienta te impide mezclarlos. Axon (Java) hace lo mismo con un bus de comandos y otro de eventos. Otras, como Kafka o RabbitMQ, solo mueven mensajes: la disciplina la pones tú, normalmente con el nombre y con quién es dueño de cada mensaje.

Para el sobre del evento (id, tipo, origen, fecha) hay un estándar de la CNCF, CloudEvents, que usan Azure Event Grid, Google Eventarc o Knative, entre otros. No dice qué datos meter dentro; eso sigue siendo decisión tuya.

Y en tu banco: la notificación que te llega al móvil tras una transferencia suele traer el importe y el destinatario. Nadie ha tenido que volver a preguntar a nadie para escribirla.

Siguiente episodio

Hasta ahora, Transferencias hacía todo en una función: llamar, mover, publicar. Con una transferencia interna es fácil, porque Cuentas mueve el dinero en una sola transacción. Una transferencia a otro banco tiene varios pasos que duran segundos o minutos, y cualquiera puede fallar.

Siguiente · Episodio 3 Coreografía vs orquestación Ana envía 100 € a una cuenta de otro banco. ¿Dejamos que cada servicio reaccione a los eventos del anterior, o ponemos a alguien a dirigir? ¿Y quién sabe cómo va su transferencia?

← Volver al mapa de BCPS Bank