viernes, 6 de julio de 2018

Los eventos nunca fallan II

Me han puesto un ejemplo de un "fallo" de evento y creo que ya entiendo algunos de los casos por los que alguien puede llegar a creer que un evento falla. Vamos a ello.

Me proponen el siguiente escenario sobresimplificado y sin Sagas ni Event Sourcing, para no complicar la cosa (no me seáis quisquillosos tanto en cuanto la verdadera segregación de contextos y cosas así, asumamos que tiene que ser de esta manera para el caso que nos ocupa):

Tenemos 2 contextos: Ventas y Contabilidad. El contexto de ventas se encarga de recibir el comando de que el usuario ha introducido su tarjeta de crédito y ha pulsado el botón de pagar un servicio.

Ventas utiliza un agregado para realiza las comprobaciones necesarias, todo está OK con respecto a su contexto (precio, servicio contratado, disponibilidad, etc). Ventas actualiza los estados de persistencia necesarios para marcar el servicio como pagado;  y luego lanza un evento a Contabilidad "UserPaidTheService" pero resulta que ocurre algún problema al cargar el dinero y Contabilidad no puede embolsárselo. ¡Hacienda le tiene bloqueada la cuenta corriente!
- ¡Pues ya está, el evento falló, hay que notificarlo a Ventas para que deshaga su estado! ¿Ves, ves? ¡Te lo dije!

Que va, que va. El problema aquí es que no hemos diseñado bien el sistema de eventos. Pulsar un botón que pone "Pagar" no es pagar, es ordenar el pago. Algo no está pagado hasta que no tenemos el dinero en nuestro bolsillo. Ventas debe lanzar un evento que sea "UserOrderedPaidThisService" una vez comprobamos que la orden de pago es correcta con respecto al contexto de Ventas. Ese evento es manejado por Contabilidad que intentara hacer el cargo, y si todo está bien lanzará el evento "UserPaidTheService". Este nuevo evento será consumido por el departamento de Ventas y actualizará el estado al estado de pagado definitivo. Si el evento es "PaymentFailed" se puede simplemente notificar al usuario si no es necesario actualizar ningún estado en persistencia.

Un detalle curioso que ha surgido colateralmente de la resolución de este ejemplo es darse cuenta de que aunque en el contexto de Ventas se han utilizado agregados para comprobar las reglas e invariantes del dominio para el pago; si no es necesario o deseable, no se tiene porque persistir ningún estado. El agregado de ventas solo nos ha retornado un OK a las reglas en forma de evento "UserOrderedPaidThisService".

Un agregado no tiene porque ser un elemento persistible, no tiene tampoco porque provocar el cambio de ningún estado. Todo eso son detalles de una cierta implementación u otra. Tanto en cuanto un agregado se dedique a comprobar la reglas de dominio es completamente aceptable como agregado.

9 comentarios:

  1. No tendrías el mismo problema si entre las comprobaciones que haces en Ventas (disponibilidad de efectivo, etc.) al disparar UserOrderedPaidThisService y el lapso de tiempo que tardas en ir a Contabilidad y volver te quedas sin efectivo porque se gastado dinero en otro servicio (se ha hecho una transferencia/hay dos tarjetas de la misma cuenta, etc)?

    Quiero decir, que sin haber una suerte de 'transacción' que englobe ambas operaciones siempre estarás expuesto a que la segunda operación falle con independencia del orden de las mismas.

    ResponderEliminar
    Respuestas
    1. Si claro. Las operaciones fallan. Los eventos no. No entiendo a donde quieres llegar... Explicate un poco mas porfa.

      Eliminar
  2. Vaya por delante que la definición de evento depende del contexto en que estemos hablando y no tengo nada claro del contexto en que estás tratando los eventos y serguramente sea un ignorante del mismo. Aún así intentaré explicarme :)

    En el artículo propones una situación con dos eventos sucesivos en el que el segundo no puede completarse obligando a 'deshacer' el anterior.

    Como tu, no veo que un evento este fallando. Ambos eventos funcionan. El problema es que tal y como propones el ejemplo, ambos eventos son 'dependientes' y al ejecutarse independientemente pueden generar una situación inconsistente.

    Salvo que la solución que propones esté dentro de algo parecido a una transacción, me parece que después de comprobar que el pago 'es posible' vas a contabilizar y si sale bien la contabilización completas el pago. Si son eventos independientes, desde que compruebas que puedes pagar hasta que terminas de contabilizar podría completarse otro pago y quedarte sin 'efectivo' para pagar después de contabilizar.

    Quiero decir que al final si tienes que modificar datos con dos eventos diferentes que en realidad son dependientes y requieren comprobaciones, siempre te puede ocurrir que el segundo de ellos (sin fallar) no puedas completarlo olbigándote a deshacer lo que hiciste en el primero.

    ResponderEliminar
    Respuestas
    1. Contabilidad carga el dinero y el banco lo pone en nuestra cuenta o no. Si lo pone lanzamos evento de OK, si no, lanzamos el evento de Fail. Ventas actua en consecuencia. ¿Para que transacciones? Ventas no tiene que deshacer nada. El usuario ha ordenado el pago si o si que es lo que comprueba Ventas. Que falla contabilidad, pues se hace reintento o se le notifica al usuario. Ventas sigue teniendo una accion de orden de pago que se guarda para saber que el usuario lo intento. ¿Que importa que el usuario haga otra orden de pago mas tarde? Se comprueba tambien, se registra y se lanza evento de ordenar el pago. No veo necesidad de transaccion por ningun lado.

      Eliminar
  3. Porque parece ser que tu estás entendiendo que el movimiento del dinero se hace al contabilizar y yo estaba entendiendo que el movimiento del dinero se hacía en la venta.

    La contabilidad apenas sería necesaria si los pagos/cobros se hiciesen en el momento que se hace el intercambio del servicio/mercancía.

    Lo normal es vender un bien o servicio, entregar una factura que genera una obligación de pago y contabilizar esa obligación que tu cliente ha adquirido contigo. En un momento posterior, se efectúa el pago y después se contabiliza que se ha satisfecho ese pago.

    En el ejemplo yo entendía que estábamos en ésa segunda parte y por tanto siempre entendía que el movimiento de dinero se producía en el evento de venta y después al contabilizar la venta no se podía.

    En el ejemplo, igualmente la contabilización podría lanzar un OK o un Fail a Ventas.

    Si 'deshacer' lo hecho no es un problema no hace falta cambiar el orden en que se hacen las cosas.

    ¿Cual es la diferencia entonces entre el ejemplo y tu propuesta de mejora?

    ResponderEliminar
    Respuestas
    1. La diferencia esta en que en el primer caso se ha persistido y se ha lanzado un evento que dice que el usuario pagó; que hay que desacer si falla el cargo de dinero. Mi mejora convierte cada agregado de un contexto en una unidad transaccional que no hay que desahacer nunca.

      Eliminar
  4. Ahora entiendo el motivo de mi confusión. Tu entiendes que 'cargas' el dinero en la contabilización y por tanto se está lanzando el evento de que el usuario pagó 'antes de tiempo'.

    Yo entendía que el movimiento del dinero comenzaba y terminaba en el primer evento y luego 'sólo' se hacía un apunte contable. Normalmente no poder contabilizar algo no es obstáculo alguno para que se complete un pago o no.

    Gracias por la explicación.

    ResponderEliminar
    Respuestas
    1. El problema de intentar poner un ejemplo sobresimplificado es que termina siendo mas lioso por la falta de detalles y matices ;)

      Eliminar
  5. La raiz del problema es intentar usar un evento como un comando. Los comandos pueden fallar por reglas de negocio. Los eventos nunca.

    ResponderEliminar