Arquitectura de microservicios basada en dominios de Uber

Aprox. transl. : Un artículo reciente de Uber Engineering habla sobre el viaje de esta gran empresa hacia su versión mejorada de la arquitectura de microservicios. Si bien algunos usuarios de Internet vieron el nuevo enfoque como “simplemente aplicar los principios de DDD a los microservicios” por una buena razón, el artículo despertó un gran interés por parte de la comunidad de desarrolladores y otros ingenieros. Y por eso, nos complace presentar su versión en ruso, preparada especialmente para habr.







Introducción



Recientemente, se han discutido activamente los inconvenientes de las arquitecturas orientadas a servicios y, en particular, las arquitecturas de microservicios (MA). Hace solo unos años, muchos estaban dispuestos a cambiarse a MA debido a sus muchas ventajas: flexibilidad en forma de implementaciones independientes, propiedad transparente, mayor estabilidad del sistema y mejor separación de preocupaciones. Sin embargo, la situación ha cambiado recientemente: el enfoque de microservicio ha comenzado a ser criticado por su tendencia a aumentar seriamente la complejidad, lo que a veces dificulta la implementación incluso de funciones triviales . (Hablamos de esto en la charla " Microservicios: el tamaño importa, incluso si tiene Kubernetes " - aprox. Transl.)



Uber tiene actualmente alrededor de 2200 microservicios críticos, y nosotros mismos hemos experimentado todos los pros y contras de este enfoque. Durante los últimos dos años, Uber ha intentado reducir la complejidad del panorama de microservicios mientras mantiene las ventajas de la arquitectura en el camino. Con esta publicación, planeamos presentar nuestro enfoque genérico a las arquitecturas de microservicio llamado Arquitectura de Microservicio Orientada al Dominio (DOMA).



Si bien ha sido popular criticar las arquitecturas de microservicios por sus deficiencias en los últimos años, pocos se han atrevido a proclamar que deberían abandonarse por completo. Sus beneficios operativos son demasiado importantes; Además, no parece haber alternativas (o extremadamente limitadas) a este enfoque. El objetivo de nuestro enfoque genérico es ayudar a las organizaciones que desean reducir la complejidad general del sistema mientras mantienen la flexibilidad de MA.



Este artículo explorará DOMA, los desafíos que llevaron a este enfoque en Uber, sus beneficios para la plataforma y los equipos de productos y, finalmente, algunos consejos para aquellos que buscan migrar a esta arquitectura.



¿Qué es un microservicio?



Los microservicios son una extensión de las arquitecturas orientadas a servicios. A diferencia de los "servicios" bastante grandes de la década de 2000, los microservicios realizan una tarea específica. Estas aplicaciones están alojadas y accesibles a través de la red y proporcionan una interfaz bien definida. Otras aplicaciones acceden a esta interfaz mediante la llamada a procedimiento remoto (RPC).



Una característica clave de MA es la forma en que se publica, invoca e implementa el código. Las aplicaciones grandes y monolíticas generalmente se dividen en componentes encapsulados con interfaces bien definidas. Luego, estas interfaces se llaman directamente dentro del proceso, en lugar de a través de la red. En este sentido, un microservicio puede verse como una especie de biblioteca con menor rendimiento (por efecto de los retrasos de la red y el tiempo en la serialización / deserialización) al llamar a cualquiera de sus funciones.



Pensando en los microservicios de esta manera, podríamos preguntarnos por qué necesitamos una arquitectura de microservicios. La respuesta clásica a esta pregunta se debe a la capacidad de implementar componentes individuales de forma independiente y escalarlos fácilmente.... En el caso de una aplicación grande y monolítica, la organización se ve obligada a implementar o liberar todo el código al mismo tiempo. Como resultado, cada nueva versión conlleva muchos cambios. Las implementaciones se vuelven riesgosas y requieren mucho tiempo. Cualquier error puede derribar todo el sistema.



Por lo tanto, las empresas se están moviendo hacia los microservicios para facilitar su uso y sacrificar el rendimiento . También deben asumir los costos adicionales de mantener la infraestructura requerida para los microservicios. La experiencia demuestra que en muchas situaciones tal compromiso tiene sentido. Al mismo tiempo, es un poderoso argumento contra una transición prematura a MA.



Motivación



En el momento de la transición a los microservicios (alrededor de 2012-2013), en Uber teníamos dos servicios monolíticos principales y enfrentamos muchos problemas operativos que los microservicios resuelven con éxito:



  • Riesgos de disponibilidad. Cualquier error en la base de código del monolito puede eliminar todo el sistema (en este caso, todo el Uber).
  • Implementaciones costosas y arriesgadas. Eran muy difíciles de llevar a cabo y, a menudo, tenían que volver a la versión anterior.
  • Mala separación de áreas de responsabilidad. Era muy difícil hacer un seguimiento de quién era responsable de qué en la colosal base de código. Con un crecimiento exponencial, la prisa a veces borró las líneas entre la lógica y los componentes.
  • Trabajo ineficiente. Los problemas anteriores en conjunto dificultan que los equipos trabajen de forma independiente o independientes entre sí.


En otras palabras, con el aumento de la cantidad de ingenieros en Uber de decenas a cientos y la aparición de una gran cantidad de equipos que poseen sus propias partes de la pila de tecnología, la arquitectura monolítica ató cada vez más el destino de estos equipos y no les permitió trabajar de forma independiente.



Por lo tanto, decidimos cambiar a MA. Como resultado, nuestros sistemas se han vuelto más flexibles y han permitido que los equipos se vuelvan más autónomos .



  • Fiabilidad del sistema. La confiabilidad general del sistema aumenta con la transición a MA. Un servicio individual puede fallar (y puede revertirse a una versión anterior) sin correr el riesgo de bloquear todo el sistema.
  • . - : « ?», — .
  • . , . , , , , .
  • . .
  • . .


No es exagerado decir que Uber no podría haber alcanzado su escala y nivel de calidad actuales sin MA.



Sin embargo, a medida que la empresa siguió creciendo y el número de ingenieros aumentó de cientos a miles, comenzamos a notar una serie de problemas asociados con el aumento significativo de la complejidad del sistema. En el caso de MA, sacrificamos una única base de código monolítica a cambio de una serie de "cajas negras" cuya funcionalidad puede cambiar en cualquier momento y provocar un comportamiento inesperado.



Por ejemplo, los ingenieros tuvieron que analizar ~ 50 servicios en 12 equipos diferentes para llegar a la raíz de un problema.



Comprender las dependencias entre los servicios puede resultar bastante difícil, ya que pueden interactuar entre sí en muchos niveles. Un aumento en los retrasos en la dependencia n-ésima puede provocar una avalancha de problemas en los servicios ascendentes. Además, sin las herramientas adecuadas, será imposible comprender qué sucedió. Todo esto hace que la depuración sea muy difícil.





Arquitectura de microservicio de Uber a mediados de 2018 por Jaeger



Para implementar la función más simple, un ingeniero a menudo tiene que trabajar con muchos servicios, mientras que equipos y personas completamente diferentes son responsables de ellos. Como resultado, se dedica mucho tiempo a organizar el trabajo en equipo, las reuniones, las consultas de diseño y la revisión del código (revisión del núcleo). El beneficio inicial de la transparencia de la propiedad se está desdibujando gradualmente a medida que los equipos invaden continuamente los servicios de los demás, cambian los modelos de datos e incluso se implementan en nombre de los propietarios de servicios. Esto puede crear monolitos de red en los que los servicios solo parecen ser independientes, pero de hecho deben implementarse juntos para poder realizar cualquier cambio de manera segura.





Un ejemplo de un sistema tan complejo en Uber (~ 2018) con diez puntos de contacto para una fácil integración (incluso antes de DOMA).



Como resultado, tenemos una desaceleración en el proceso de desarrollo, la inestabilidad afecta a los propietarios del servicio, migraciones que requieren más tiempo, etc. Por desgracia, no hay vuelta atrás para las organizaciones que ya se han cambiado a MA. La situación está perfectamente ilustrada por la conocida frase: " Es imposible vivir con ellos y no puedes dispararles ".



Arquitectura de microservicio de dominio específico



Piense en los microservicios como bibliotecas vinculadas a E / S y la arquitectura de microservicios como una gran aplicación distribuida. En este caso, podemos utilizar soluciones arquitectónicas conocidas para pensar en la mejor forma de organizar nuestro código.



Por lo tanto, una arquitectura de microservicio orientada al dominio (DOMA) puede depender de formas bien establecidas de organizar el código, como el diseño orientado al dominio , la arquitectura limpia , la arquitectura orientada a servicios y los patrones de desarrollo orientados a objetos e interfaces.Consideramos que DOMA es innovador en el sentido de que es una forma relativamente nueva de aprovechar los principios de diseño existentes en los sistemas distribuidos globalmente de las grandes organizaciones .



A continuación, se muestran algunos conceptos básicos de DOMA y terminología relacionada:



  1. En lugar de mirar microservicios individuales, buscamos grupos de ellos. Y los llamamos dominios (dominios) .
  2. A continuación, combinamos los dominios de las llamadas capas (las capas) . La capa a la que pertenece un dominio determina qué dependencias están disponibles para los microservicios en ese dominio. Llamamos a la arquitectura resultante de la multicapa (de diseño de capas) .
  3. , . (gateways).
  4. , , , 'hardcode' , . (, - ), (extension architecture) .


En otras palabras, la arquitectura estructurada, las puertas de enlace de dominio y los puntos de extensibilidad DOMA prediseñados transforman las arquitecturas de microservicio de algo complejo a algo comprensible y tangible: un conjunto estructurado de componentes flexibles, reutilizables y en capas.



El resto de este artículo se centrará en la implementación de DOMA por parte de Uber y sus beneficios. También se brindarán consejos prácticos a las empresas que deseen adoptar este enfoque.



Implementación en Uber



Dominios



Los dominios de Uber son colecciones de uno o más microservicios que están vinculados en función de una combinación lógica de funciones. Naturalmente, surge la pregunta de qué tan grande debe ser el dominio. En este caso, no estamos dando ninguna instrucción. Algunos dominios pueden incluir docenas de servicios, otros solo uno. Aquí es importante pensar detenidamente sobre el papel lógico de cada asociación. Por ejemplo, hemos agrupado los servicios de búsqueda en el mapa, los servicios de tarifas, los servicios de selección (comparando conductores y pasajeros) en dominios separados. Además, no siempre repiten la estructura organizativa de la empresa. Uber Maps se divide en tres dominios con 80 microservicios ocultos detrás de tres puertas de enlace diferentes.



Arquitectura basada en capas



La arquitectura multicapa responde a la pregunta de qué servicio y cuál se puede comunicar dentro de los límites de MA Uber. Es decir, puede verse como una distribución global de áreas de responsabilidad o como un mecanismo para la gestión global de la dependencia.



La arquitectura en capas ayuda a comprender el radio de daño después de fallas y refleja la especificidad del producto en términos de la cantidad de servicios dependientes de Uber. A medida que se mueve de abajo hacia arriba, el número de servicios afectados en caso de falla se reduce y el ámbito de aplicación del producto se reduce . Y viceversa, una mayor cantidad de servicios depende de la funcionalidad en los niveles inferiores, por lo tanto, el radio de daño como resultado de una falla es, por regla general, mayor y la gama de tareas comerciales a resolver es más amplia. La siguiente figura ilustra este concepto.





Puede imaginar que los niveles superiores están enfocados en funciones responsables de una experiencia de usuario específica (estrecha) (por ejemplo, funciones móviles), mientras que los niveles inferiores están habitados por funciones comerciales más globales (por ejemplo, administración de cuentas o viajes a través del mercado de viajes compartidos). ... Cada capa solo depende de las capas subyacentes, lo que da claridad a conceptos como el radio de explosión y la integración de dominio.



Vale la pena señalar que la funcionalidad a menudo se mueve hacia abajo en este gráfico, de estrecha a más amplia. Puede imaginar una función simple que se vuelve más importante ("plataforma") con el tiempo a medida que evolucionan los requisitos. De hecho, se espera este tipo de migración descendente, y muchas de las principales plataformas comerciales de Uber comenzaron como una función para conductores o pasajeros, y con el tiempo ha crecido y se ha generalizado más como nuevos negocios (como Uber Eats o Uber Freight ) y conectarles más dependencias.



Dentro de Uber, distinguimos los siguientes cinco niveles.



  1. . , . — Uber , .
  2. -. , Uber , , Rides (), Eats ( ) Freight ( ).
  3. . , , . , «request a ride» ( ) , Rides: Rider, Rider «Lite», m.uber.com, ..
  4. . , (/), .
  5. . Uber . .


Como puede ver, cada nivel subsiguiente representa una combinación de funciones cada vez más estrecha y tiene un radio de impacto más pequeño (en otras palabras, menos componentes dependen de la funcionalidad dentro de esta capa).



Pasarelas



El término puerta de enlace API ya está bien establecido en las arquitecturas de microservicios. Nuestra definición no es muy diferente de la establecida, excepto que tendemos a pensar en las puertas de enlace como un único punto de entrada al grupo de servicios correspondiente (que llamamos dominio ). El éxito de una puerta de enlace depende de una arquitectura API bien diseñada:





este diagrama ilustra el diseño de alto nivel de la puerta de enlace. Se abstrae de los detalles de la estructura interna de los dominios: un conjunto de servicios, tablas con datos, canalizaciones ETL, etc. Otros dominios tienen acceso solo a interfaces: API para llamadas a procedimientos remotos, eventos y solicitudes en el sistema de mensajería.



Dado que los consumidores ascendentes solo se ejecutan en un servicio, las puertas de enlace brindan numerosos beneficios en términos de migraciones futuras , capacidad de detección y una reducción general de la complejidad del sistema cuando los servicios ascendentes tienen solo una dependencia (en lugar de depender de múltiples servicios descendentes que puede existir en el dominio). Cuando se ve desde una perspectiva de diseño OO, las puertas de enlace son definiciones de interfaz y nos permiten hacer lo que queramos con una "implementación" interna (es decir, un grupo de microservicios).



Extensiones



Extensiones (extensiones) , como su nombre lo indica, es un mecanismo para expandir dominios. La definición básica de dicho complemento es que proporciona un mecanismo para extender la funcionalidad de un servicio sin cambiar las partes internas de ese servicio o afectar su confiabilidad general. En nuestro Uber tiene dos modelos de expansión: el lógico (extensiones lógicas) y en base a datos (datos las extensiones) . El concepto de extensión nos permitió escalar la arquitectura para que varios equipos pudieran trabajar de forma independiente entre sí.



Extensiones lógicas



Las extensiones lógicas proporcionan un mecanismo para extender la lógica subyacente de un servicio. Para ellos, utilizamos una especie de patrón de proveedor o complemento con una interfaz que se define por separado para cada servicio. Esto permite a los equipos implementar su lógica utilizando solo la interfaz y sin interferir con el código de la plataforma principal.



Supongamos, por ejemplo, que el controlador está en línea. Por lo general, realizamos varias verificaciones para asegurarnos de que se permite tener un estado en línea (por seguridad, cumplimiento, etc.). Cada uno tiene su propio equipo. Una forma posible de hacer esto es forzar a cada comando a escribir lógica en el mismo punto final, pero esto puede agregar complejidad. Cada verificación requerirá una lógica diferente, y sin ninguna relación.



En el caso de extensiones lógicas de punto final llamadas conectarsedefinirá la interfaz a la que se espera que se ajuste cada extensión con una solicitud y un tipo de respuesta predefinidos. Cada equipo registrará una extensión que se encargará de implementar esta lógica. En este caso, simplemente pueden tomar cierta información sobre el controlador y devolver un valor lógico (bool) , que determinará si el controlador es "digno" del estado en línea o no. Y el punto final en sí (conectarse) simplemente iterará sobre estas respuestas y establecerá si alguna de ellas es falsa .



Este enfoque separa el código central de las extensiones y proporciona aislamiento entre ellas. Sin embargo, las extensiones no saben qué otra lógica se está ejecutando. Esto facilita la creación de funciones adicionales, por ejemplo, para la observación o el marcado de características .



Extensiones basadas en datos



Este tipo de extensión proporciona un mecanismo para adjuntar datos arbitrarios a la interfaz para evitar sobrecargar innecesariamente los modelos de datos de la plataforma subyacente. En las extensiones de datos, usamos activamente funciones como Any de Protobuf, que permiten agregar datos arbitrarios a las solicitudes. Los servicios a menudo almacenan estos datos o los pasan a una extensión lógica, de modo que la plataforma principal nunca deserialice (y por lo tanto no "sepa" nada) sobre este contexto arbitrario. Cualquier implementación incurre en una sobrecarga de infraestructura a cambio de una tipificación más sólida. Una alternativa más simple es el formato JSON para representar cualquier dato:





Complementos arbitrarios



Además de las extensiones booleanas y de datos, muchos equipos de Uber han desarrollado plantillas de extensiones personalizadas para que coincidan con sus dominios. Por ejemplo, la mayoría de las integraciones relacionadas con la arquitectura de presentación utilizan la lógica de ejecución de tareas basada en DAG.



Beneficios



DOMA ha influido en casi todos los negocios importantes de Uber en un grado u otro. Durante el año pasado, nos hemos centrado principalmente en la capa empresarial. Proporciona una lógica generalizada para las distintas líneas de negocio de una empresa.



DOMA es relativamente nuevo para Uber, y en el futuro definitivamente compartiremos más información y ejemplos de nuestra arquitectura. Los primeros resultados fueron alentadores: simplificaron enormemente el trabajo de los desarrolladores y redujeron la complejidad general del sistema.



Productos y plataformas



DOMA es el resultado de un esfuerzo de colaboración entre los diversos equipos de productos y plataformas de Uber. En muchos casos, los costos de soporte de la plataforma se han reducido en un orden de magnitud. Los equipos de productos se han beneficiado de la especificidad y el desarrollo acelerado.



Por ejemplo, uno de los primeros consumidores de la plataforma de nuestra arquitectura de extensión pudo reducir el tiempo para priorizar e integrar una nueva función de tres días a tres horas al reducir los tiempos de revisión del código, programar y acelerar la educación del consumidor.



Complejidad reducida



Anteriormente, los equipos de productos tenían que trabajar con muchos servicios posteriores dentro de un dominio, pero ahora solo necesitan llamar a uno. Al reducir el número de puntos de contacto al introducir una nueva función, el tiempo de implementación se ha reducido en un 25-30%. Además, pudimos distribuir 2200 servicios en 70 dominios. Aproximadamente la mitad de ellos se han implementado, y para la mayoría existe un plan de implementación de una forma u otra.



Migraciones futuras



En Uber, hemos calculado que el microservicio tiene una vida media de 1,5 años. Es decir, cada año y medio el 50% de nuestros servicios pierde relevancia. Sin puertas de enlace, una arquitectura de microservicio puede convertirse en un infierno de migración. Los microservicios en constante cambio requieren migraciones ascendentes constantes. Las puertas de enlace permiten a los equipos evitar dependencias de los servicios de dominio descendentes, lo que significa que estos servicios pueden cambiar sin tener que migrar a ascendentes.



Dos de las mayores actualizaciones de la plataforma de Uber durante el año pasado ocurrieron detrás de las puertas de enlace. Estas plataformas tienen cientos de servicios dependientes y, sin puertas de enlace, todos los consumidores existentes tendrían que ser migrados. Sería increíblemente caro, por lo que un rediseño completo de la plataforma sería poco realista.



Nuevas líneas de negocio y productos



Los marcos basados ​​en DOMA han demostrado ser mucho más extensibles y más fáciles de mantener. La mayoría de los equipos de Uber que cambiaron a DOMA lo hicieron porque resultaba demasiado caro mantener nuevas líneas de negocio.



Consejo practico



En esta sección, he recopilado algunos consejos prácticos para empresas que podrían estar interesadas en DOMA. El principio rector aquí es que, según nuestra experiencia, una arquitectura de microservicio madura y reflexiva se basa en cambios incrementales en la dirección correcta en el momento adecuado. En realidad, es casi imposible "reescribir" completamente MA.



Por lo tanto, vemos la evolución de MA como una especie de proceso de "cortar un seto", gracias al cual crece en la dirección correcta, y no como un esfuerzo voluntario de una sola vez. Es un proceso dinámico y gradual.



Inauguración



Las preguntas clave aquí son: "¿Cuándo deberíamos mudarnos a MA?" y "¿Tiene esto sentido para nuestra organización?" Como vimos anteriormente, si bien los microservicios brindan una ventaja operativa en organizaciones con una gran cantidad de ingenieros, también aumentan la complejidad general, lo que puede dificultar la implementación de nuevas funciones.



En organizaciones pequeñas, es poco probable que la ventaja operativa compense la mayor complejidad arquitectónica. Además, los MA generalmente requieren recursos de ingeniería dedicados para respaldarlos, lo que puede ser demasiado costoso para una empresa en etapa inicial o simplemente subóptimo en términos de priorización.



Dicho esto, sería conveniente posponer la transición a los microservicios por un tiempo. Si la organización decide cambiar a microservicios, le recomendamos que utilice la analogía de una gran aplicación distribuida y piense de antemano en dividir las áreas problemáticas entre los servicios. También tenga en cuenta que es probable que los primeros microservicios sean los más importantes y duraderos, ya que describen una parte clave del negocio.



Mediana empresa



La utilidad de MA aumenta en empresas medianas con muchos equipos, donde las líneas de responsabilidad se difuminan paulatinamente entre diferentes funciones y plataformas.



Aquí es donde puede empezar a pensar en la jerarquía de microservicios. La gestión de dependencias puede pasar a primer plano, ya que algunos servicios pueden volverse mucho más críticos para el funcionamiento de una empresa y más equipos dependerán de ellos.



Las primeras inversiones en plataformas pueden pagar dividendos más adelante. La creación de plataformas de negocio que no dependan de otros productos permite evitar la acumulación de deuda técnica y la penetración de lógicas de producto arbitrarias en los principales servicios de la plataforma. Quizás en esta etapa debería introducirse un mecanismo de extensión para lograr este objetivo.



Dado que la cantidad de microservicios aún es pequeña, es posible que no tenga sentido agruparlos todavía. Sin embargo, vale la pena señalar aquí que un dominio en el contexto de la implementación de DOMA en Uber bien puede incluir un solo servicio, por lo que un tren de pensamiento "orientado al dominio" aún no está de más.



Grandes negocios



Las grandes organizaciones de ingeniería pueden tener cientos de especialistas, microservicios y muchas dependencias. Es en estas condiciones que DOMA alcanza su máximo potencial. Seguramente, estas empresas tendrán grupos obvios de microservicios que se pueden combinar fácilmente en dominios con pasarelas frente a ellos. Los servicios heredados a menudo necesitan refactorización / reescritura y migración posterior. Esto significa que las puertas de enlace pronto comenzarán a brindar beneficios reales en términos de facilidad de migración (si, por supuesto, ya están implementadas).



También aumentará la importancia de una jerarquía transparente y comprensible: algunos servicios serán "producto" para determinadas funciones o grupos de funciones, mientras que otros admitirán múltiples productos y actuarán como "plataformas". En esta etapa, es fundamental mantener la lógica del producto arbitraria separada de las plataformas para evitar un estrés operativo masivo en los equipos de la plataforma y minimizar el riesgo de inestabilidad global del sistema.



Pensamientos finales



En Uber, continuamos desarrollando DOMA activamente a medida que más equipos migran a él. La idea principal detrás de DOMA es que una arquitectura de microservicio es solo un gran programa distribuido. Y se pueden aplicar los mismos principios a su evolución que a cualquier otro software. DOMA es solo un enfoque para el pensamiento práctico sobre estos principios. Esperamos que le resulte útil y esperamos sus comentarios.



DOMA en sí es el resultado de un esfuerzo multifuncional de casi 60 ingenieros de todo Uber. Me gustaría expresar un agradecimiento especial a las siguientes personas por sus contribuciones a este trabajo durante los últimos 2 años:



Alex Zylman, Alexandre Wilhelm, Allen Lu, Ankit Srivastava, Anthony Tran, Anupam Dikshit, Anurag Biyani, Daniel Wolf, Deepti Chedda, Dmitriy Bryndin, Gaurav Tungatkar, Jacob Greenleaf, Jaikumar Ganesh, Jennie Ngyabuae, Joeoshinier , Kusha Kapoor, Linda Fu, Madan Thangavelu, Nimish Sheth, Parth Shah, Shawn Burke, Simon Newton, Steve Sherwood, Uday Kiran Medisetty y Waleed Kadous.



Agradecimientos: Este trabajo ha combinado muchos patrones de diseño existentes en la industria para resolver problemas en Uber y también sugirió algunos patrones nuevos (como extensiones). Agradecemos a la industria por trabajar en ellos. También estamos agradecidos a los ingenieros de Linkedin que trabajaron en Superblocks por compartir sus experiencias con nosotros.



PD del traductor



Lea también en nuestro blog:






All Articles