Manejo de cambios en una arquitectura de microservicios controlada por eventos

9

Estoy haciendo un proyecto de investigación donde estoy investigando las opciones para manejar los cambios en una arquitectura de microservicios basada en eventos.

Entonces, digamos que tenemos una aplicación donde obtuvimos cuatro servicios diferentes. Cada uno de estos servicios tiene una base de datos propia para almacenar datos locales.

En esta configuración, los cuatro servicios se comunican entre sí mediante un bus de eventos. Entonces, cuando algo sucede en un servicio, publica un evento. Todos los demás servicios que estén interesados ​​en ese evento lo procesarán a su manera.

En ese caso, los diferentes servicios de la arquitectura deben tener "contratos" sobre el contenido de estos eventos (atributos, etc.). Por lo tanto, los servicios tienen "dependencias débilmente acopladas" a estos eventos

Mi pregunta es: ¿Cómo podemos manejar los cambios en estos eventos?

Entonces, digamos que el servicio A registra nuevos usuarios en la aplicación. Por lo tanto, envía un evento "" Registrado por el usuario ". El servicio B recoge ese evento y lo procesa. Pero algunos desarrolladores del equipo del servicio C decidieron que también necesitan un género de un usuario registrado. Por lo tanto, el evento se cambia y el género del atributo se agrega al evento "UserRegistered".

¿Cómo podemos asegurarnos de que el Servicio B aún pueda recoger el mismo evento con ese atributo adicional sin volver a implementar?

¿Y hay otras formas de abordar este problema luego de versionar estos eventos?

CGeense
fuente
¿De qué formato son sus mensajes o es algo que puede diseñar? Algunos formatos de mensajes permiten atributos opcionales. Dependiendo de la implementación del lector, puede agregar atributos opcionales sin necesidad de actualizar todos los lectores.
Thomas Owens
Soy libre de elegir un formato para usar en mis mensajes. Creo que usar JSON es la mejor manera de hacerlo. Es importante que estos diferentes servicios se construyan en diferentes idiomas. Por eso es necesario un formato general como XML o JSON.
CGeense

Respuestas:

1

Los eventos no son sobre lo que cambió. Se trata de cuando algo cambió.

Puedo crear un sistema de eventos completamente desacoplado de los contenidos que cambiaron. De esa manera, todo lo que aprendo de un evento es que un objeto ha sido actualizado. Si incluso me importa que el objeto se haya actualizado, le diré lo que sepa cómo hablar con ese objeto para preguntarle qué cambió.

Eso no resuelve el problema de comunicar estos cambios. Simplemente evita que se convierta en parte del sistema de eventos.

Un ejemplo de una forma de resolver el problema de las diferentes versiones de datos es hacer que el observador cree y entregue una colección al objeto observado. El objeto observado llena la colección con sus últimos datos y cuando el control regresa, usted (el observador) tiene lo que necesita. Si hay algo adicional que no le importa, porque nunca lo escuchó, simplemente ignórelo.

Muchas otras formas de desollar a ese gato, pero esa es una que he hecho funcionar exactamente en este caso.

naranja confitada
fuente
¿No aumentaría esto dramáticamente el tráfico entre servicios? Usando el ejemplo en la pregunta, un UserRegisteredevento, si hubiera un evento que no contuviera información sobre el usuario, habría 1 mensaje publicado en el bus y luego {número de servicios interesados} solicitudes al servicio de usuario o mensajes publicados al autobús. Entonces, habría {cantidad de servicios interesados} mensajes de varios tamaños. Aunque creo que este es probablemente un diseño más limpio en papel, si el rendimiento es preocupante, se descompone en cualquier sistema no trivial, especialmente a través de una red.
Thomas Owens
@ThomasOwens Enviar datos con el evento significa que si tengo N observadores, envío N mensajes. Enviar el evento solo significa que envío 3N mensajes de los cuales solo 1 tiene el paquete de datos. Eso escala bien incluso en una red. El único inconveniente importante es que triplica tu retraso. No digo que no puedas encontrar una solución más óptima para una situación particular. Estoy demostrando que los sistemas de eventos y las versiones de datos no tienen que estar acoplados.
candied_orange
1
La idea de este bus de eventos es desacoplar los diferentes servicios. Al usar una pieza de middleware, podemos asegurarnos de que estos servicios no se conozcan y puedan existir y comunicarse sin saber de la existencia del otro. Si eliminamos el estado de estos eventos y permitimos que los servicios se conecten entre sí directamente, estamos acoplando estos servicios. De esa manera, nunca podemos volver a implementar un solo servicio sin tener que
volver a implementar
1
El punto aquí es que ese sistema de eventos o no necesita datos extensibles. JSON o XML lo hacen bien si no va a cambiar los nombres o las estructuras que se han establecido previamente. He hecho lo mismo con las colecciones. El sistema de eventos no debería preocuparse por los géneros. Si está enviando datos, simplemente debe pasarlos y algo en el otro extremo se preocupará por los géneros o no.
candied_orange
1

Los marcos como NServiceBus manejan esto mediante el uso de versiones de eventos con envío de mensajes polimórficos.

Por ejemplo, la versión 1 del Servicio A podría publicar un evento como IUserRegistered_v1. Cuando el Servicio A versión 1.1 necesita incluir un campo adicional, podría declarar la interfaz IUserRegistered_v1_1, que heredaría de IUserRegistered_v1, así como declarar algunos campos adicionales.

Cuando el Servicio A publica un evento IUserRegistered_v1_1, NServiceBus enviará el mensaje a todos los puntos finales que manejan IUserRegistered_v1 o IUserRegistered_v1_1.

pnschofield
fuente
0

Mejora incremental

Un cambio simple en el modelo es que cuando los oyentes se registran como observadores, incluyen una lista u otra estructura de los elementos de datos que desean conocer. Esto puede funcionar si los datos devueltos por el servicio son simples, pero si tiene una buena cantidad de datos jerárquicos, esto puede ser realmente complicado de implementar.

Roca sólida

Si realmente desea una forma sólida de hacer este diseño, el servicio mantiene un historial de cambios que se han realizado en los datos que almacena. Esencialmente, nunca actualiza los registros en su base de datos, agrega nuevos registros donde cada uno representa el cambio. Cada uno de estos nuevos registros está asociado a una identificación de evento que identifica la acción. Se almacena un registro par con toda la información relevante sobre el cambio (quién, qué, cuándo, etc.) Esto tiene algunos otros beneficios que están fuera del alcance de esta respuesta pero que se discuten en este artículo sobre el teorema de CAP .

Cuando se realiza un cambio, crea el registro de eventos y agrega todos los datos nuevos a su base de datos. Luego publica un evento para los oyentes que contiene (mínimamente) la identificación del evento. Los oyentes pueden solicitar los datos asociados con esa identificación y obtener la versión de los datos asociados con ella. Cada oyente puede obtener lo que necesita sin acoplar las necesidades de otros oyentes diferentes. Le aconsejaría que agregue un subconjunto de los campos de datos más utilizados al mensaje de evento para que los oyentes puedan filtrar los eventos que no les interesan. Esto puede reducir la charla del proceso y algunos oyentes nunca necesitarán llamar de vuelta en absoluto. Esto también lo protege de los problemas de tiempo. Si solo vuelve a llamar al servicio y obtiene los datos según la clave, puede haber otros cambios que ocurrieron entre obtener el evento y recuperar los datos para él. Es posible que esto no sea importante para todos los oyentes, pero puede crear grandes problemas si necesita conocer todos los cambios. La mejora de diseño incremental anterior es compatible con este enfoque si realmente desea convertirlo en 11.

Algo de esto puede ser excesivo para lo que necesita hacer, pero en mi experiencia si no tiene una forma precisa de ver cómo cambia un registro con el tiempo, usted o alguien que trabaje con sus datos eventualmente lo querrá.

JimmyJames
fuente
-1

@CandiedOrange hace un punto válido en un comentario a su propia respuesta con respecto a los formatos de datos extensibles como xml.

No debería importar mientras agregue datos. Sin embargo, proporcione valores predeterminados razonables para eventos antiguos / campos no obligatorios.

Solo debe actualizar los servicios que le interesan, en este caso, el género. Un analizador xml / json debería poder ignorar datos adicionales para los otros servicios. Esto depende de su elección de analizador y formato de datos de eventos, por supuesto.

Sin embargo, no estoy de acuerdo con los eventos que no tienen los datos relevantes. Para el abastecimiento de eventos, los eventos deben definir lo que ha cambiado. Al recibir un evento, otros servicios no deberían tener que recuperar datos de la fuente del evento.

Sander Weerdenburg
fuente
Mi punto es que necesita manejar todo tipo de cambios. Piense en un servicio que transmite un evento. Y ese evento contiene una propiedad que está obsoleta y debe eliminarse. Incluso si estos otros servicios no están utilizando la propiedad, los romperá simplemente porque lo están esperando. Entonces leí este artículo en martinfowler.com sobre contratos impulsados ​​por el consumidor: martinfowler.com/articles/consumerDrivenContracts.html Al aplicar este principio. Cada proveedor (evento) sabe lo que se espera de él. con esa información él puede validar si rompe algún consumidor.
CGeense