Antipatrones de arquitectura orientada a eventos

¡Hola de nuevo! En previsión del inicio del curso "Arquitecto de software", preparamos una traducción de otro material interesante.




Los últimos años han visto un aumento en la popularidad de la arquitectura de microservicios. Hay muchos recursos que le enseñan cómo implementarlo correctamente, pero a menudo la gente habla de eso como un grupo de plata. Existen muchos argumentos en contra del uso de microservicios, pero el más importante de ellos es que este tipo de arquitectura está cargada de complejidad incierta, cuyo nivel depende de cómo gestione la relación entre sus servicios y equipos. Puede encontrar mucha literatura que explicará por qué (tal vez) en su caso, los microservicios no serán la mejor opción.

En letgo migramos de un monolito a microservicios para satisfacer la necesidad de escalabilidad, e inmediatamente nos convencimos de su efecto beneficioso en el trabajo de los equipos. Cuando se usan correctamente, los microservicios nos han dado varias ventajas, a saber:

  • : , . ( ..) . ( ) Users.
  • : , . . , , , , . .

-


No todas las arquitecturas de microservicios están controladas por eventos. Algunas personas abogan por la comunicación sincrónica entre los servicios en esta arquitectura utilizando HTTP (gRPC, REST, etc.). En letgo, tratamos de no seguir este patrón y asociar asincrónicamente nuestros servicios con eventos de dominio . Estas son las razones por las que hacemos esto:

  • : . , DDoS . , DDoS . , . .



  • (bulkheads) – , , .
  • : . , , , , . , , API, , , , . Users , Chat.

En base a esto, en letgo intentamos adherirnos a la comunicación asincrónica entre servicios, y sincronizado solo funciona en casos excepcionales como la función MVP. Hacemos esto porque queremos que cada servicio genere sus propias entidades basadas en eventos de dominio publicados por otros servicios en nuestro Bus de mensajes.

En nuestra opinión, el éxito o el fracaso en la implementación de una arquitectura de microservicio depende de cómo lidie con su complejidad inherente y de cómo interactúan sus servicios entre sí. Dividir el código sin transferir la infraestructura de comunicación a asíncrono convertirá su aplicación en un monolito distribuido.



Arquitectura dirigida por eventos en letgo

Hoy quiero compartir un ejemplo de cómo usamos eventos de dominio y comunicación asíncrona en letgo: nuestra entidad de usuario existe en muchos servicios, pero su creación y edición es procesada inicialmente por el servicio de usuarios. En la base de datos del servicio de Usuarios, almacenamos muchos datos, como nombre, dirección de correo electrónico, avatar, país, etc. En nuestro servicio de Chat, también tenemos un concepto de usuario, pero no necesitamos los datos que tiene la entidad Usuario del servicio de Usuarios. El nombre de usuario, avatar e ID (enlace al perfil) se muestran en la lista de cuadros de diálogo. Decimos que en un chat solo hay una proyección de la entidad Usuario, que contiene datos parciales. De hecho, en Chat no estamos hablando de usuarios, los llamamos "conversadores". Esta proyección se refiere al servicio de Chat y se basa en los eventos que Chat recibe del servicio de Usuarios.

Hacemos lo mismo con los listados. En el servicio de Productos, almacenamos n imágenes de cada listado, pero en la vista de lista de cuadros de diálogo, mostramos uno principal, por lo tanto, nuestra proyección de Productos a Chat requiere solo una imagen en lugar de n.


Ver una lista de diálogos en nuestro chat. Muestra qué servicio específico en el backend proporciona información.

Si vuelve a mirar la lista de cuadros de diálogo, verá que casi todos los datos que mostramos no son creados por el servicio de Chat, sino que pertenecen a él, porque las proyecciones de Usuario y Chat son propiedad de Chat. Existe una compensación entre la accesibilidad y la coherencia de las proyecciones, que no discutiremos en este artículo, pero solo diré que es claramente más fácil escalar muchas bases de datos pequeñas que una grande.


Vista simplificada de la arquitectura letgo

Antipatrones


Algunas soluciones intuitivas a menudo se convirtieron en errores. Aquí hay una lista de los antipatrones más importantes que encontramos en nuestra arquitectura relacionada con el dominio.

1. Eventos gruesos
Intentamos que nuestros eventos de dominio sean lo más pequeños posible sin perder su valor de dominio. Deberíamos haber sido cuidadosos al refactorizar bases de código heredadas con entidades grandes y cambiar a arquitectura de eventos. Dichas entidades pueden llevarnos a grandes eventos, pero dado que nuestros eventos de dominio se transformaron en un contrato público, necesitábamos hacerlos lo más simples posible. En este caso, la refactorización se ve mejor desde un lado. Para comenzar, diseñamos nuestros eventos utilizando la técnica de tormenta de eventos.y luego refactorice el código de servicio para adaptarlo a nuestros eventos.

También deberíamos ser más cuidadosos con el problema del "producto y usuario": muchos sistemas usan entidades de productos y usuarios, y estas entidades, como regla, extraen toda la lógica detrás de ellos, y esto significa que todos los eventos de dominio están asociados con ellos.

2. Eventos como intenciones Un
evento de dominio, por definición, es un evento que ya ha ocurrido. Si publica algo en el bus de mensajes para solicitar lo que sucedió en algún otro servicio, lo más probable es que esté ejecutando un comando asincrónico en lugar de crear un evento de dominio. Como regla general, nos referimos a eventos de dominio pasados: ser_registered , product_publishedetc. Cuanto menos un servicio sepa sobre el otro, mejor. El uso de eventos como comandos vincula los servicios y aumenta la probabilidad de que un cambio en un servicio afecte a otros servicios.

3. Falta de serialización o compresión independiente Los
sistemas de serialización y compresión de eventos en nuestra área temática no deberían depender del lenguaje de programación. Ni siquiera necesita saber en qué idioma se escriben los servicios al consumidor. Es por eso que podemos usar serializadores Java o PHP, por ejemplo. Permita que su equipo pase tiempo discutiendo y eligiendo un serializador, porque cambiarlo en el futuro será difícil y requerirá mucho tiempo. En letgo usamos JSON, sin embargo, hay muchos otros formatos de serialización con buen rendimiento.

4. Falta de estructura estándar
Cuando comenzamos a portar el backend letgo a una arquitectura orientada a eventos, acordamos una estructura común para eventos de dominio. Se ve algo como esto:

{
  “data”: {
    “id”: [uuid], // event id.type”: “user_registered”,
    “attributes”: {
      “id”: [uuid], // aggregate/entity id, in this case user_id
      “user_name”: “John Doe”,
    }
  },
  “meta” : {
    “created_at”: timestamp, // when was the event created?
    “host”: “users-service” // where was the event created?
  }
}

Tener una estructura común para nuestros eventos de dominio nos permite integrar rápidamente servicios e implementar algunas bibliotecas con abstracciones.

5. Falta de validación del esquema
Durante la serialización, en Letgo experimentamos problemas con los lenguajes de programación sin una tipificación fuerte.


{
  “null_value_one”: null, // thank god
  “null_value_two”: “null”,
  “null_value_three”: “”,
}

Una cultura de prueba bien establecida que garantiza la serialización de nuestros eventos, y una comprensión de cómo funciona la biblioteca de serialización, ayuda a hacer frente a esto. En letgo estamos cambiando a Avro y al Registro de Esquemas Confluentes, que nos proporciona un solo punto para determinar la estructura de los eventos de nuestro dominio y evita errores de este tipo, así como documentación obsoleta.

6. Eventos de dominio anémico
Como dije antes, y como su nombre lo indica, los eventos de dominio deben tener un valor a nivel de dominio. Así como tratamos de evitar la inconsistencia de los estados en nuestras entidades, debemos evitar esto en los eventos de dominio. Vamos a ilustrar esto con el siguiente ejemplo: el producto en nuestro sistema tiene geolocalización con latitud y longitud, que se almacenan en dos campos diferentes de la tabla de productos del servicio Productos. Todos los productos se pueden "mover", por lo que tendremos eventos de dominio para presentar esta actualización. Anteriormente, para esto teníamos dos eventos: product_latitude_updated y product_longitude_updated , que no tenía mucho sentido si no era una torre en un tablero de ajedrez. En este caso, los eventos product_location_updated tendrán más sentido.o product_moved .


Una torre es una pieza de ajedrez. Solía ​​llamarse una gira. Una torre solo puede moverse vertical u horizontalmente a través de cualquier número de campos desocupados.

7. Falta de herramientas de depuración
En letgo producimos miles de eventos de dominio por minuto. Todos estos eventos se convierten en un recurso extremadamente útil para comprender lo que está sucediendo en nuestro sistema, registrar la actividad del usuario o incluso reconstruir el estado de un sistema en un momento específico mediante la búsqueda de eventos. Necesitamos usar hábilmente este recurso, y para esto necesitamos herramientas para verificar y depurar nuestros eventos. Las solicitudes como "muéstrame todos los eventos generados por John Doe en las últimas 3 horas" también pueden ser útiles para detectar fraudes. Para estos fines, hemos desarrollado algunas herramientas en ElasticSearch, Kibana y S3.

8. Falta de monitoreo de eventos
Podemos usar eventos de dominio para probar el estado del sistema. Cuando implementamos algo (que ocurre varias veces al día según el servicio), necesitamos herramientas para verificar rápidamente la operación correcta. Por ejemplo, si implementamos una nueva versión del servicio Productos en producción y vemos una disminución en el número de eventos publicados por el producto20%, es seguro decir que rompimos algo. Actualmente estamos usando InfluxDB, Grafana y Prometheus para lograr esto con funciones derivadas. Si recuerda el curso de las matemáticas, comprenderá que la derivada de la función f (x) en el punto x es igual a la tangente del ángulo tangente dibujado en la gráfica de la función en este punto. Si tiene una función para publicar la velocidad de un evento específico en un área temática y toma una derivada de ella, verá los picos de esta función y puede establecer notificaciones basadas en ellos. Usando estas notificaciones, puede evitar frases como "avisarme si publicamos menos de 200 eventos por segundo durante 5 minutos" y centrarse en un cambio significativo en la velocidad de publicación.


Algo extraño sucedió aquí ... O tal vez es solo una campaña de marketing

9. La esperanza de que todo esté bien
Estamos tratando de crear sistemas sostenibles y reducir el costo de su restauración. Además de los problemas de infraestructura y el factor humano, una de las cosas más comunes que pueden afectar la arquitectura de eventos es la pérdida de eventos. Necesitamos un plan con el que podamos restaurar el estado correcto del sistema al volver a procesar todos los eventos que se perdieron. Aquí, nuestra estrategia se basa en dos puntos:

  • : , « , », - , . letgo Data, Backend.
  • : - . , , , message bus . – , , . , user_registered Users, , MySQL, user_id . user_registered, , . , , - MySQL ( , 30 ). -, DynamoDB. , , , . , , , , .

10. Falta de documentación sobre eventos de dominio
Nuestros eventos de dominio se han convertido en nuestra interfaz pública para todos los sistemas en el back-end. Del mismo modo que documentamos nuestras API REST, también debemos documentar los eventos de dominio. Cualquier empleado de la organización debe poder ver la documentación actualizada para cada evento de dominio publicado por cada servicio. Si usamos esquemas para verificar eventos de dominio, también se pueden usar como documentación.

11. Resistencia al consumo de eventos propios.
Se le permite e incluso se le recomienda utilizar sus propios eventos de dominio para crear proyecciones en su sistema, que, por ejemplo, están optimizadas para la lectura. Algunos equipos se resistieron a este concepto, porque se limitaron al concepto de consumo de los eventos de otras personas.

¡Te veo en el curso!

All Articles