Patrón de higo estrangulador
Intención
El patrón de higo estrangulador ayuda a migrar una aplicación monolítica a una arquitectura de microservicios de manera gradual, con un riesgo menor de transformación y una disrupción menor empresarial.
Motivación
Las aplicaciones monolíticas se desarrollan para proporcionar la mayor parte de sus funciones en un solo proceso o contenedor. El código está acoplado de manera ajustada. Por este motivo, para hacer cambios en las aplicaciones son necesarias nuevas pruebas exhaustivas para evitar problemas de regresión. Los cambios no se pueden probar de manera aislada, lo que afecta a la duración del ciclo. A medida que la aplicación se enriquece con más características, la alta complejidad puede implicar que se dedique más tiempo al mantenimiento, un aumento del tiempo de comercialización y, en consecuencia, una ralentización de la innovación de los productos.
Cuando el tamaño de la aplicación se escala, aumenta la carga cognitiva del equipo y puede provocar que los límites de responsabilidad del equipo no estén claros. No es posible escalar las características individuales según la carga. Es necesario escalar toda la aplicación para permitir la carga máxima. A medida que los sistemas envejecen, la tecnología puede quedar obsoleta, lo que aumenta los costos de soporte. Las aplicaciones antiguas y monolíticas siguen las prácticas recomendadas que estaban disponibles en el momento del desarrollo y no se diseñaron para distribuirse.
Cuando se migra una aplicación monolítica a una arquitectura de microservicios, se puede dividir en componentes más pequeños. Estos componentes se pueden escalar de manera independiente, se pueden lanzar de manera independiente y pueden ser responsabilidad de equipos individuales. Esto da lugar a una mayor velocidad de cambio, ya que los cambios se localizan y se pueden probar y publicar de manera rápida. Los cambios tienen un ámbito de impacto menor porque los componentes están acoplados de manera débil y se pueden implementar de manera individual.
Sustituir por completo un monolito por una aplicación de microservicios mediante la reescritura o la refactorización del código es una tarea enorme y un riesgo grande. Una migración a gran escala, en la que el monolito se migra en una sola operación, supone un riesgo de transformación y una disrupción empresarial. Mientras se refactoriza la aplicación, es extremadamente difícil o incluso imposible agregar características nuevas.
Una manera de resolver este problema es utilizar el patrón de higo estrangulador, que introdujo Martin Fowler. Este patrón implica pasar a los microservicios mediante la extracción gradual de características y la creación de una aplicación nueva en torno al sistema existente. Las características del monolito se sustituyen de manera gradual por microservicios. Los usuarios de las aplicaciones pueden utilizar las características recién migradas de manera progresiva. Cuando se trasladen todas las características al sistema nuevo, la aplicación monolítica se podrá retirar de manera segura.
Aplicabilidad
Utilice el patrón de higo estrangulador cuando suceda lo siguiente:
-
Desea migrar la aplicación monolítica de manera gradual a una arquitectura de microservicios.
-
Un enfoque de migración radical es riesgoso debido al tamaño y la complejidad del monolito.
-
La empresa quiere agregar características nuevas y no puede esperar a que se complete la transformación.
-
Los usuarios finales deben verse afectados lo menos posible durante la transformación.
Problemas y consideraciones
-
Acceso a la base del código: para implementar el patrón de higo estrangulador, debe tener acceso a la base del código de la aplicación monolítica. A medida que se vayan migrando las características del monolito, será necesario hacer cambios pequeños en el código e implementar una capa anticorrupción en el monolito para enrutar las llamadas a microservicios nuevos. No puede interceptar llamadas sin acceso a la base del código. El acceso a la base del código también es fundamental para redirigir las solicitudes entrantes. Es posible que sea necesario refactorizar el código para que la capa del proxy pueda interceptar las llamadas de las características migradas y enrutarlas a los microservicios.
-
Dominio poco claro: la descomposición prematura de los sistemas puede resultar costosa, sobre todo cuando el dominio no está claro y es posible definir mal los límites de los servicios. El diseño basado en dominios (DDD) es un mecanismo para comprender el dominio. La tormenta de eventos es una técnica para determinar los límites del dominio.
-
Identificación de microservicios: puede utilizar el DDD como una herramienta clave para identificar los microservicios. Para identificar los microservicios, busque las divisiones naturales entre las clases de servicios. Numerosos servicios tendrán su propio objeto de acceso a los datos y se desacoplarán con facilidad. Los servicios que tienen una lógica empresarial relacionada y las clases que no tienen dependencias o que tienen pocas dependencias son buenos candidatos para convertirse en microservicios. Puede refactorizar el código antes de descomponer el monolito para evitar un acoplamiento ajustado. También debe tener en cuenta los requisitos de cumplimiento, la frecuencia de publicación, la ubicación geográfica de los equipos, las necesidades de escala, las necesidades tecnológicas basadas en casos de uso y la carga cognitiva de los equipos.
-
Capa anticorrupción: durante el proceso de migración, cuando las características del monolito tengan que llamar a las características que se migraron como microservicios, se debe implementar una capa anticorrupción (ACL) que dirija cada llamada al microservicio correspondiente. Con el fin de desacoplar a los autores de las llamadas existentes en el monolito y evitar que se produzcan cambios en estos, la ACL funciona como un adaptador o una fachada que convierte las llamadas en una interfaz más nueva. Esto se analiza en detalle en una sección anterior sobre la implementación del patrón de ACL que figura en esta guía.
-
Error en la capa del proxy: durante la migración, una capa proxy intercepta las solicitudes que van a la aplicación monolítica y las dirige al sistema heredado o al sistema nuevo. Sin embargo, esta capa del proxy puede convertirse en un solo punto de error o en un obstáculo para el rendimiento.
-
Complejidad de la aplicación: los monolitos grandes son los que más se benefician del diseño de higo estrangulador. En el caso de las aplicaciones pequeñas, en las que la complejidad de una refactorización completa es baja, podría resultar más eficiente volver a escribir la aplicación en una arquitectura de microservicios en lugar de migrarla.
-
Interacciones de servicios: los microservicios se pueden comunicar de manera sincrónica o asíncrona. Cuando se es necesaria una comunicación sincrónica, considere si los tiempos de espera pueden provocar el consumo de conexiones o del grupo de subprocesos y provocar que las aplicaciones tengan problemas de rendimiento. En esos casos, utilice el patrón de disyuntores para devolver el error inmediato en el caso de las operaciones en las que se puedan producir errores durante periodos prolongados. La comunicación asíncrona se puede lograr mediante el uso de eventos y colas de mensajes.
-
Agregación de datos: en una arquitectura de microservicios, los datos se distribuyen en las bases de datos. Cuando sea necesario agregar datos, puede utilizar AWS AppSync
en el frontend o el patrón de división de responsabilidades de consulta de comandos (CQRS) en el backend. -
Coherencia de datos: los microservicios tienen su propio almacén de datos y la aplicación monolítica también puede utilizar estos datos. Para permitir el uso compartido, puede sincronizar el almacén de datos de los microservicios nuevos con la base de datos de la aplicación monolítica mediante una cola y un agente. Sin embargo, esto puede provocar redundancia de datos y una coherencia final entre dos almacenes de datos, por lo que le recomendamos que lo trate como una solución táctica hasta que pueda establecer una solución a largo plazo, como un lago de datos.
Implementación
Según el patrón del higo estrangulador, se sustituye una funcionalidad específica por un servicio o una aplicación nuevos, un componente a la vez. Una capa del proxy intercepta las solicitudes que van a la aplicación monolítica y las dirige al sistema heredado o al sistema nuevo. Como la capa del proxy dirige a los usuarios a la aplicación correcta, puede agregar características al sistema nuevo y, al mismo tiempo, garantizar que siga funcionando el monolito. Con el tiempo, el sistema nuevo sustituirá todas las características del sistema anterior y podrá retirarlo del servicio.
Arquitectura de alto nivel
En el diagrama siguiente, una aplicación monolítica tiene tres servicios: servicio de usuario, servicio de carrito y servicio de cuentas. El servicio de carrito depende del servicio de usuario y la aplicación utiliza una base de datos relacional monolítica.
El primer paso es agregar una capa del proxy entre la interfaz de usuario de la tienda y la aplicación monolítica. Al principio, el proxy enruta todo el tráfico a la aplicación monolítica.
Cuando quiera agregar características nuevas a la aplicación, debe implementarlas como microservicios nuevos en lugar de agregar características al monolito existente. Sin embargo, sigue corrigiendo errores en el monolito para garantizar la estabilidad de la aplicación. En el diagrama siguiente, la capa del proxy enruta las llamadas al monolito o al microservicio nuevo según la URL de la API.
Introducción de una capa anticorrupción
En la arquitectura siguiente, el servicio de usuario se migró a un microservicio. El servicio de carrito llama al servicio de usuario, pero la implementación deja de estar disponible en el monolito. Además, es posible que la interfaz del servicio recién migrado no coincida con la interfaz anterior de la aplicación monolítica. Para abordar estos cambios, debe implementar una ACL. Durante el proceso de migración, cuando las características del monolito tienen que llamar a las características que se migraron como microservicios, la ACL convierte las llamadas a la interfaz nueva y las enruta al microservicio correspondiente.
Puede implementar la ACL dentro de la aplicación monolítica como una clase específica del servicio que se migró. Por ejemplo, UserServiceFacade o UserServiceAdapter. La ACL debe retirarse una vez que todos los servicios dependientes se hayan migrado a la arquitectura de microservicios.
Cuando se utiliza la ACL, el servicio de carrito sigue llamando al servicio de usuario en el monolito y el servicio de usuario redirige la llamada al microservicio a través de la ACL. El servicio de carrito debe seguir llamando al servicio de usuario sin saber de la migración del microservicio. Este acoplamiento débil es necesario para reducir la regresión y la disrupción empresarial.
Gestión de la sincronización de los datos
Se recomienda que el microservicio debe ser responsable de sus datos. El servicio de usuario almacena sus datos en su propio almacén de datos. Es posible que tenga que sincronizar los datos con la base de datos monolítica para gestionar las dependencias, como la generación de informes, y para que las aplicaciones posteriores que aún no están preparadas puedan acceder de manera directa a los microservicios. Es posible que la aplicación monolítica también requiera los datos para otras funciones y componentes que aún no se migraron a los microservicios. Por lo tanto, es necesaria la sincronización de datos entre el microservicio nuevo y el monolito. Para sincronizar los datos, puede introducir un agente de sincronización entre el microservicio del usuario y la base de datos monolítica, como se muestra en el diagrama siguiente. El microservicio de usuario envía un evento a la cola cada vez que se actualiza su base de datos. El agente de sincronización escucha la cola y actualiza de manera continua la base de datos monolítica. Por último, los datos de la base de datos monolítica son coherentes con los datos que se sincronizan.
Migración de servicios adicionales
Cuando el servicio de carrito se migra de la aplicación monolítica, su código se revisa para llamar de manera directa al servicio nuevo, de modo que la ACL ya no enrute esas llamadas. En el siguiente diagrama se ilustra esta arquitectura.
En el diagrama siguiente se muestra el estado final de estrangulamiento, en el que todos los servicios se migraron del monolito y solo queda el esqueleto del monolito. Los datos históricos se pueden migrar a los almacenes de datos que son responsabilidad de los servicios individuales. La ACL se puede eliminar y el monolito está listo para su retirada en esta etapa.
En el diagrama siguiente se muestra la arquitectura final tras la retirada de la aplicación monolítica. Puede alojar los microservicios individuales a través de una URL basada en recursos (por ejemplo http://www.storefront.com/user) o a través de su propio dominio (por ejemplo http://user.storefront.com), según los requisitos de la aplicación. Para más información sobre los métodos principales para exponer las API HTTP a los consumidores principales mediante nombres de host y rutas, consulte la sección Patrones de enrutamiento de API.
Implementación mediante los servicios de AWS
Uso de API Gateway como proxy de la aplicación
En el diagrama siguiente se ilustra el estado inicial de la aplicación monolítica. Supongamos que se migró a AWS con una estrategia de lift-and-shift, por lo que se ejecuta en una instancia de Amazon Elastic Compute Cloud (Amazon EC2)
En la arquitectura siguiente, AWS Migration Hub Refactor Spaces implementa Amazon API Gateway
El servicio de usuario se migra a una función de Lambda y una base de datos de Amazon DynamoDB
En el diagrama siguiente, el servicio de carrito también se migró del monolito a una función de Lambda. Se agregan una ruta y un punto de conexión de servicio adicionales a Refactor Spaces. El tráfico pasa de manera automática a la función de Lambda Cart. Amazon ElastiCache
En el diagrama siguiente, el último servicio (cuenta) se migra del monolito a una función de Lambda. Sigue utilizando la base de datos de Amazon RDS original. Ahora, la arquitectura nueva tiene tres microservicios con bases de datos independientes. Cada servicio utiliza un tipo de base de datos distinto. Este concepto de uso de bases de datos personalizadas para satisfacer las necesidades específicas de los microservicios se denomina persistencia políglota. Las funciones de Lambda también se pueden implementar en distintos lenguajes de programación, según lo determine el caso de uso. Durante la refactorización, Refactor Spaces automatiza la transición y el enrutamiento del tráfico a Lambda. Esto ahorra a los desarrolladores el tiempo necesario para diseñar, implementar y configurar la infraestructura de enrutamiento.
Uso de varias cuentas
En la implementación anterior, utilizamos una VPC única con una subred privada y una pública para la aplicación monolítica, e implementamos los microservicios en la misma Cuenta de AWS por motivos de simplicidad. Sin embargo, esto es poco frecuente en situaciones reales, en las que los microservicios se suelen implementar en varias Cuentas de AWS para lograr una implementación independiente. En una estructura de varias cuentas, es necesario configurar el tráfico de enrutamiento desde el monolito a los servicios nuevos en cuentas distintas.
Refactor Spaces ayuda a crear y configurar la infraestructura de AWS para que las llamadas de la API se desvíen de la aplicación monolítica. Refactor Spaces orquesta políticas de API Gateway
Supongamos que los servicios de usuario y de carrito se implementan en dos cuentas distintas, como se muestra en el diagrama siguiente. Cuando utiliza Refactor Spaces, solo es necesario configurar el punto de conexión del servicio y la ruta. Refactor Spaces automatiza la integración entre API Gateway y Lambda y la creación de políticas de recursos de Lambda, para que pueda centrarse en refactorizar los servicios de manera segura fuera del monolito.
Para ver un tutorial en video sobre el uso de Refactor Spaces, consulte Refactor Apps Incrementally with AWS Migration Hub Refactor Spaces