Dado el servicio A (CMS) que controla un modelo (producto, supongamos que los únicos campos que tiene son id, título, precio) y los servicios B (envío) y C (correos electrónicos) que tienen que mostrar el modelo dado cuál debería ser el enfoque. sincronizar la información del modelo dado en esos servicios en el enfoque de abastecimiento de eventos? Supongamos que el catálogo de productos rara vez cambia (pero cambia) y que hay administradores que pueden acceder a los datos de envíos y correos electrónicos con mucha frecuencia (las funcionalidades de ejemplo son: B: display titles of products the order contained
y C:) display content of email about shipping that is going to be sent
. Cada uno de los servicios tiene su propia base de datos.
Solución 1
Enviar toda la información requerida sobre el Producto dentro del evento; esto significa la siguiente estructura para order_placed
:
{
order_id: [guid],
product: {
id: [guid],
title: 'Foo',
price: 1000
}
}
En el servicio B y C, la información del producto se almacena en el product
atributo JSON en la orders
tabla
Como tal, para mostrar la información necesaria solo se utilizan los datos recuperados del evento
Problemas : dependiendo de qué otra información se deba presentar en B y C, la cantidad de datos en el evento puede crecer. B y C pueden no requerir la misma información sobre el Producto, pero el evento tendrá que contener ambos (a menos que separemos los eventos en dos). Si los datos dados no están presentes dentro del evento dado, el código no puede usarlo; si agregaremos una opción de color al Producto dado, para los pedidos existentes en B y C, el producto dado será incoloro a menos que actualicemos los eventos y luego los volvamos a ejecutar .
Solución 2
Enviar solo el guid del producto dentro del evento; esto significa la siguiente estructura para order_placed
:
{
order_id: [guid],
product_id: [guid]
}
En los servicios B y C, la información del producto se almacena en el product_id
atributo de la orders
tabla
La información del producto es recuperada por los servicios B y C cuando es necesario al realizar una llamada API al A/product/[guid]
punto final
Problemas : esto hace que B y C dependan de A (en todo momento). Si el esquema del Producto cambia en A, los cambios deben hacerse en todos los servicios que dependen de ellos (de repente)
Solución 3
Enviar solo el guid del producto dentro del evento; esto significa la siguiente estructura para order_placed:
{
order_id: [guid],
product_id: [guid]
}
En los servicios B y C, la información del producto se almacena en la products
tabla; todavía hay product_id
en la orders
mesa, pero hay replicación de products
datos entre A, B y C; B y C pueden contener información diferente sobre el Producto que A
La información del producto se siembra cuando se crean los servicios B y C y se actualizan cada vez que la información sobre los Productos cambia haciendo una llamada al A/product
punto final (que muestra la información requerida de todos los productos) o realizando un acceso directo de base de datos a A y copiando la información necesaria del producto requerida para Servicio.
Problemas : esto hace que B y C dependan de A (cuando se siembra). Si el esquema de Producto cambia en A, los cambios deben hacerse en todos los servicios que dependen de ellos (cuando se inicia)
Según tengo entendido, el enfoque correcto sería ir con la solución 1, y actualizar el historial de eventos según cierta lógica (si el catálogo del Producto no ha cambiado y queremos agregar color para mostrar, podemos actualizar el historial de forma segura para obtener el estado actual de productos y rellenar datos faltantes dentro de los eventos) o atender la inexistencia de datos dados (si el catálogo de productos ha cambiado y queremos agregar el color que se mostrará, no podemos estar seguros si en ese momento en el pasado producto dado tenía un color o no, podemos suponer que todos los productos en el catálogo anterior eran negros y se atienden al actualizar eventos o código)
fuente
updating event history
: en el evento de abastecimiento, el historial de eventos es su fuente de verdad y nunca debe modificarse, sino que solo debe avanzar. Si los eventos cambian, puede usar versiones de eventos o soluciones similares, pero al reproducir sus eventos hasta un punto específico en el tiempo, el estado de los datos debe ser como era en ese punto.updating event history
quiero decir: revisa todos los eventos, copiándolos de un flujo (v1) a otro flujo (v2) para mantener un esquema de eventos consistente.display image at the point when purchase was made
) o no puede (representa la intención dedisplay current image as it within catalog
)Respuestas:
La solución n. ° 3 se acerca mucho a la idea correcta.
Una forma de pensar en esto: B y C son copias en caché "locales" de los datos que necesitan. Los mensajes procesados en B (y también en C) usan la información almacenada localmente en caché. Asimismo, los informes se producen utilizando la información almacenada localmente en caché.
Los datos se replican desde el origen a los cachés a través de una API estable. B y C ni siquiera necesitan usar la misma API: usan el protocolo de búsqueda apropiado para sus necesidades. En efecto, definimos un contrato (protocolo y esquema de mensaje) que restringe al proveedor y al consumidor. Entonces, cualquier consumidor para ese contrato puede conectarse a cualquier proveedor. Los cambios incompatibles hacia atrás requieren un nuevo contrato.
Los servicios eligen la estrategia de invalidación de caché adecuada para sus necesidades. Esto puede significar extraer cambios de la fuente en un horario regular, o en respuesta a una notificación de que las cosas pueden haber cambiado, o incluso "a pedido", actuando como una lectura a través de la memoria caché, volviendo a la copia almacenada de los datos cuando La fuente no está disponible.
Esto le brinda "autonomía", en el sentido de que B y C pueden continuar entregando valor comercial cuando A no está disponible temporalmente.
Lectura recomendada: Datos externos, Datos internos , Pat Helland 2005.
fuente
Hay dos cosas difíciles en informática, y una de ellas es la invalidación de caché.
La solución 2 es absolutamente mi posición predeterminada, y generalmente solo debería considerar implementar el almacenamiento en caché si se encuentra con uno de los siguientes escenarios:
Los problemas de rendimiento son realmente el principal impulsor. Hay muchas formas de resolver el n. ° 2 que no implican el almacenamiento en caché, como garantizar que el Servicio A esté altamente disponible.
El almacenamiento en caché agrega una complejidad significativa a un sistema y puede crear casos extremos que son difíciles de razonar y errores que son muy difíciles de replicar. También debe mitigar el riesgo de proporcionar datos obsoletos cuando existen datos más nuevos, que pueden ser mucho peor desde una perspectiva comercial que (por ejemplo) mostrar un mensaje que dice "El servicio A está inactivo; intente nuevamente más tarde".
De este excelente artículo de Udi Dahan:
Además, si necesita consultar en un momento dado los datos del producto, esto debe manejarse de la manera en que los datos se almacenan en la base de datos del producto (por ejemplo, fechas de inicio / finalización), debe exponerse claramente en la API (la fecha efectiva debe ser una entrada para la llamada API para consultar los datos).
fuente
Es muy difícil simplemente decir que una solución es mejor que la otra. Elegir uno entre la Solución # 2 y # 3 depende de otros factores (duración de caché, tolerancia de consistencia, ...)
Mis 2 centavos:
La invalidación de caché puede ser difícil, pero la declaración del problema menciona que el catálogo de productos rara vez cambia. Este hecho hace que los datos del producto sean un buen candidato para el almacenamiento en caché
Solución # 1 (NOK)
Solución # 2 (OK)
Solución n. ° 3 (Complejo pero preferido)
fuente
En términos generales, recomendaría encarecidamente la opción 2 debido al acoplamiento temporal entre esos dos servicios (a menos que la comunicación entre estos servicios sea súper estable y no sea muy frecuente). El acoplamiento temporal es lo que usted describe
this makes B and C dependant upon A (at all times)
, y significa que si A está inactivo o inalcanzable desde B o C, B y C no pueden cumplir su función.Personalmente, creo que ambas opciones 1 y 3 tienen situaciones en las que son opciones válidas.
Si la comunicación entre A y B y C es tan alta, o la cantidad de datos necesarios para ingresar al evento es lo suficientemente grande como para preocuparlo, entonces la opción 3 es la mejor opción, porque la carga en la red es mucho menor , y la latencia de las operaciones disminuirá a medida que disminuya el tamaño del mensaje. Otras preocupaciones a considerar aquí son:
Sin embargo, la opción 1 no es algo que descartaría. Hay la misma cantidad de acoplamiento, pero en cuanto al desarrollo, debería ser fácil de hacer (sin necesidad de acciones especiales), y la estabilidad del dominio debería significar que estos no cambiarán con frecuencia (como ya mencioné).
Otra opción que sugeriría es una ligera variación a 3, que no es ejecutar el proceso durante el inicio, sino observar un evento "ProductAdded y" ProductDetailsChanged "en B y C, cuando hay un cambio en el catálogo de productos en A. Esto haría que sus implementaciones sean más rápidas (y más fáciles de solucionar un problema / error si encuentra alguno).
Editar 2020-03-03
Tengo un orden específico de prioridades al determinar el enfoque de integración:
Si el costo de la inconsistencia es alto (básicamente, los datos del producto en A deben ser consistentes lo antes posible con el producto almacenado en caché en B y C), entonces no puede evitar tener que aceptar la no disponibilidad y realizar una solicitud sincrónica (como una web / solicitud de descanso) de B & C a A para obtener los datos. ¡Ten cuidado! Esto todavía no significa transaccionalmente consistente, sino que minimiza las ventanas por inconsistencia. Si absolutamente, positivamente, tiene que ser inmediatamente consistente, necesita retomar los límites de su servicio. Sin embargo, yo muy firmemente que esto no debería ser un problema. Por experiencia, en realidad es extremadamente raro que la compañía no pueda aceptar algunos segundos de inconsistencia, por lo que ni siquiera debería necesitar hacer solicitudes sincrónicas.
Si necesita consultas puntuales (que no noté en su pregunta y, por lo tanto, no incluí anteriormente, tal vez erróneamente), el costo de mantener esto en los servicios posteriores es muy alto (necesitaría duplicar lógica de proyección de eventos internos en todos los servicios posteriores) que deja en claro la decisión: debe dejar la propiedad a A, y consultar A ad-hoc sobre solicitud web (o similar), y A debe usar el abastecimiento de eventos para recuperar todos los eventos que conoce en el momento de proyectar al estado y devolverlo. Supongo que esta puede ser la opción 2 (si lo entendí correctamente), pero los costos son tales que, si bien el acoplamiento temporal es mejor que el costo de mantenimiento de eventos duplicados y la lógica de proyección.
Si no necesita un punto en el tiempo, y no hay un propietario claro y único de los datos (que en mi respuesta inicial supuse esto en función de su pregunta), entonces un patrón muy razonable sería mantener representaciones del producto en cada servicio por separado. Cuando actualiza los datos de los productos, actualiza A, B y C en paralelo al hacer solicitudes web paralelas a cada uno, o tiene una API de comandos que envía múltiples comandos a cada uno de A, B y C. B y C usan su versión local de los datos para hacer su trabajo, que puede o no estar obsoleta. Esta no es ninguna de las opciones anteriores (aunque podría hacerse para estar cerca de la opción 3), ya que los datos en A, B y C pueden diferir, y el "todo" del producto puede ser una composición de los tres datos fuentes.
Saber si la fuente de la verdad es un contrato estable es útil porque puede usarlo para usar el dominio / eventos internos (o eventos que almacena en su fuente de eventos como patrón de almacenamiento en A) para la integración entre A y los servicios B y C. Si el contrato es estable, puede integrarse a través de los eventos de dominio. Sin embargo, entonces tiene una preocupación adicional en el caso de que los cambios sean frecuentes, o que el contrato de mensaje sea lo suficientemente grande como para que el transporte sea una preocupación.
Si tiene un propietario claro, con un contrato que se espera que sea estable, las mejores opciones serían la opción 1; una orden contendría toda la información necesaria y luego B y C cumplirían su función utilizando los datos del evento.
Si es probable que el contrato cambie o se rompa con frecuencia, siguiendo su opción 3, recurrir a las solicitudes web para obtener datos del producto es en realidad una mejor opción, ya que es mucho más fácil mantener múltiples versiones. Entonces B haría una solicitud en la v3 del producto.
fuente
ProductAdded
oProductDetailsChanged
agregar complejidad al seguimiento de los cambios en el catálogo de productos, necesitamos mantener esa información sincronizada entre las bases de datos de alguna manera, en caso de que los eventos se reproduzcan y necesitemos acceder a los datos del catálogo del pasado.