database - microservicios - microservices vs soa
Consistencia de DB con microservicios (4)
Esto es súper agradable y elegante, pero en la práctica se vuelve un poco complicado.
Lo que significa "en la práctica" es que necesita diseñar sus microservicios de tal manera que se cumpla con la consistencia comercial necesaria al seguir la regla:
los servicios no pueden conectarse directamente a una base de datos "propiedad" de otro servicio.
En otras palabras, no haga suposiciones sobre sus responsabilidades y cambie los límites según sea necesario hasta que pueda encontrar la manera de hacer que eso funcione.
Ahora, a tu pregunta:
¿Cuáles son los mejores patrones para mantener las cosas consistentes y vivir una vida feliz?
Para las cosas que no requieren una consistencia inmediata, y la actualización de los puntos de lealtad parece caer en esa categoría, podría usar un patrón de publicación / sub confiable para enviar eventos de un microservicio para que sean procesados por otros. El bit confiable es que querrías buenos reintentos, reversión e idempotencia (o transaccionalidad) para el procesamiento de eventos.
Si está ejecutando en .NET, algunos ejemplos de infraestructura que admiten este tipo de confiabilidad incluyen NServiceBus y MassTransit . Revelación completa - Soy el fundador de NServiceBus.
Actualización: siguiendo los comentarios sobre inquietudes acerca de los puntos de fidelidad: "si las actualizaciones de saldo se procesan con retraso, un cliente puede en realidad pedir más artículos de los que tiene puntos".
Muchas personas luchan con este tipo de requisitos para una consistencia fuerte. La cuestión es que este tipo de escenarios generalmente se pueden abordar mediante la introducción de reglas adicionales, como si un usuario termina con puntos de fidelidad negativos, notifíquelos. Si T pasa sin que se hayan solucionado los puntos de fidelidad, notifique al usuario que se le cobrará M en función de alguna tasa de conversión. Esta política debe ser visible para los clientes cuando usan puntos para comprar cosas.
¿Cuál es la mejor manera de lograr la consistencia de DB en sistemas basados en microservicio?
En el GOTO de Berlín , Martin Fowler hablaba de microservicios y una de las "reglas" que mencionó fue mantener las bases de datos "por servicio", lo que significa que los servicios no pueden conectarse directamente a una base de datos "propiedad" de otro servicio.
Esto es súper agradable y elegante, pero en la práctica se vuelve un poco complicado. Supongamos que tiene algunos servicios:
- una interfaz
- un servicio de gestión de pedidos
- un servicio de fidelización
Ahora, un cliente realiza una compra en su frontend, que llamará al servicio de gestión de pedidos, que guardará todo en la base de datos, no hay problema. En este punto, también habrá una llamada al servicio del programa de lealtad para que acredite / debite puntos de su cuenta.
Ahora, cuando todo está en el mismo servidor DB / DB, todo se vuelve fácil ya que puede ejecutar todo en una transacción: si el servicio del programa de lealtad no se escribe en la base de datos, podemos deshacer todo el proceso.
Cuando hacemos operaciones de DB a través de múltiples servicios, esto no es posible, ya que no confiamos en una conexión / aprovechamos la ejecución de una sola transacción. ¿Cuáles son los mejores patrones para mantener las cosas consistentes y vivir una vida feliz?
¡Estoy ansioso por escuchar tus sugerencias! ... ¡Y gracias de antemano!
Estoy de acuerdo con lo que dijo @Udi Dahan. Sólo quiero añadir a su respuesta.
Creo que debe continuar con la solicitud del programa de fidelización para que, si falla, se pueda realizar en otro momento. Hay varias maneras de redactar / hacer esto.
1) Hacer recuperable el fallo de la API del programa de lealtad. Es decir, puede persistir las solicitudes para que no se pierdan y puedan recuperarse (volver a ejecutarse) en algún momento posterior.
2) Ejecutar las solicitudes del programa de fidelización de forma asíncrona. Es decir, persistir primero la solicitud en algún lugar y luego permitir que el servicio la lea desde este almacén persistente. Solo eliminar de la tienda persistente cuando se ejecuta con éxito.
3) Haga lo que Udi dijo, y colóquelo en una buena cola (pub / sub patrón para ser exactos). Por lo general, esto requiere que el suscriptor realice una de estas dos cosas ... o bien persistir en la solicitud antes de eliminarla de la cola (goto 1) --OR-- primero tomar prestada la solicitud de la cola, luego, después de procesar con éxito la solicitud, solicitarla eliminado de la cola (esta es mi preferencia).
Los tres logran lo mismo. Mueven la solicitud a un lugar persistente donde se puede trabajar hasta que se complete con éxito. La solicitud nunca se pierde y se reintenta si es necesario hasta que se alcanza un estado satisfactorio.
Me gusta usar el ejemplo de una carrera de relevos. Cada servicio o pieza de código debe tomar posesión de la solicitud antes de permitir que la pieza de código anterior la deje ir. Una vez que se entrega, el propietario actual no debe perder la solicitud hasta que sea procesada o entregada a otra pieza de código.
Incluso para las transacciones distribuidas, puede entrar en el estado de "transacción en duda" si uno de los participantes se bloquea en medio de la transacción. Si diseñas los servicios como operaciones idempotentes, la vida se vuelve un poco más fácil. Uno puede escribir programas para cumplir con las condiciones del negocio sin XA. Pat Helland ha escrito un excelente artículo sobre este llamado "Life Beyond XA". Básicamente, el enfoque es hacer los supuestos mínimos sobre las entidades remotas como sea posible. También ilustró un enfoque llamado Open Nested Transactions ( http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf ) para modelar los procesos de negocios. En este caso específico, la transacción de compra sería un flujo de nivel superior y la lealtad y la gestión de pedidos serán flujos de nivel siguiente. El truco es crear servicios granulares como servicios idempotentes con lógica de compensación. Entonces, si algo falla en cualquier parte del flujo, los servicios individuales pueden compensarlo. Así que, por ejemplo, si la orden falla por alguna razón, la lealtad puede deducir el punto acumulado de esa compra.
Otro enfoque es modelar utilizando la consistencia eventual utilizando CALM o CRDT. He escrito un blog para resaltar el uso de CALM en la vida real: http://shripad-agashe.github.io/2015/08/Art-Of-Disorderly-Programming Puede ser que te ayude.
Normalmente no trato con microservicios, y esta podría no ser una buena manera de hacer las cosas, pero aquí hay una idea:
Para reafirmar el problema, el sistema consta de tres partes independientes pero que se comunican: el frontend, el backend de gestión de pedidos y el backend del programa de lealtad. El frontend quiere asegurarse de que se guarda algún estado tanto en el backend de gestión de pedidos como en el backend del programa de fidelización.
Una posible solución sería implementar algún tipo de confirmación en dos fases :
- Primero, la interfaz coloca un registro en su propia base de datos con todos los datos. Llama a esto el registro frontend .
- El frontend solicita al backend de gestión de pedidos un ID de transacción y le pasa los datos necesarios para completar la acción. El backend de gestión de pedidos almacena estos datos en un área de preparación, asociando con ellos un nuevo ID de transacción y devolviéndolo a la interfaz.
- El ID de transacción de gestión de pedidos se almacena como parte del registro de frontend.
- El frontend solicita al backend del programa de lealtad un ID de transacción, y le pasa los datos necesarios para completar la acción. El backend del programa de lealtad almacena estos datos en un área de preparación, asociándolos con un nuevo ID de transacción y devolviéndolos a la interfaz.
- El ID de transacción del programa de lealtad se almacena como parte del registro de interfaz.
- El frontend le indica al backend de gestión de pedidos para finalizar la transacción asociada con la ID de transacción que almacenó el frontend.
- El frontend le indica al backend del programa de lealtad que finalice la transacción asociada con la ID de transacción que almacenó el frontend.
- El frontend borra su registro frontend.
Si esto se implementa, los cambios no serán necesariamente atómicos , pero eventualmente serán consistentes . Pensemos en los lugares donde podría fallar:
- Si falla en el primer paso, ningún dato cambiará.
- Si falla en el segundo, tercero, cuarto o quinto, cuando el sistema vuelve a estar en línea, puede escanear todos los registros del frontend, buscando registros sin un ID de transacción asociado (de cualquier tipo). Si se encuentra con un registro de este tipo, puede volver a reproducirse desde el paso 2. (Si hay un error en el paso 3 o 5, quedarán algunos registros abandonados en los backends, pero nunca se moverá fuera del área de preparación, por lo que está bien.)
- Si falla en el sexto, séptimo u octavo paso, cuando el sistema vuelve a estar en línea, puede buscar todos los registros de frontend con ambos ID de transacción completados. Luego, puede consultar a los backends para ver el estado de estas transacciones: confirmadas o no confirmadas. . Dependiendo de lo que se haya confirmado, se puede reanudar desde el paso apropiado.