¿Tu App no escala? Microservicios al rescate
Sarah es la tech leader del grupo de desarrolladores de una aplicación llamada White Kitchens, una app de pedidos de comida. Los usuarios pueden pedir a restaurantes ficticios y la aplicación se encarga de contactar a los dark kitchens (restaurantes ficticios) de la zona para poder preparar la comida. En el último tiempo la aplicación ha sido un éxito y la cantidad de clientes está aumentando de forma exponencial.
Todos los fines de quarter terminan su sprint con un paso a producción de los cambios generados aunque cruzando los dedos para que todo salga bien. Últimamente el desarrollo no ha sido el más óptimo, los desarrolladores trabajan en funcionalidades de forma separada para luego pasarlos a su gran repositorio y generalmente los pasos a producción pueden demorar un par de días. Camino a su trabajo, Sarah recibe una llamada, hay conflictos en los cambios lo cuál si no lo solucionan podrían afectar el módulo de pedidos y la aplicación quedar inutilizable. Pero por otro lado, los directores están presionando para que los cambios salgan lo antes posible.
Al ser desarrollada como una pequeña aplicación, los programadores decidieron comenzar con una arquitectura monolítica, la cuál podríamos diagramar como un hexágono de dos niveles. En el nivel interior se define toda la lógica del negocio, es decir, la lógica de los pedidos, pagos, usuarios, cocinas, notificaciones entre otros. Por otro lado, el hexágono exterior funciona como una capa de comunicación donde la aplicación se conecta a su base de datos, conversa con las aplicaciones internas de las dark kitchens y también proporciona la interfaz para comunicarse con la aplicación móvil.
Como es una aplicación monolítica, eso ha forzado a los desarrolladores a trabajar directamente en un solo repositorio general mediante ramas de cada programador. Esto tiene otras implicaciones como lo es generar un solo pipeline de deployment a producción por lo que todo se controla de forma centralizada y unificada.
Esto tiene claros problemas de escalabilidad para la organización. En cierta forma las arquitecturas monolíticas sirven para el inicio de las aplicaciones dado su bajo costo, pero mientras más complejas son las lógicas, más difícil es mantener el proyecto. Esto genera que si una pequeña parte del código falla, toda la aplicación queda inutilizable. Por otro lado, cuando la organización crece y aumenta el número de programadores, se hace difícil mantener una armonía en el desarrollo. Los conflictos en el código se vuelven comunes en el día a día y eso gasta tiempo y performance en el equipo.
Entonces, ¿Cómo hacemos que el equipo de Sarah escale y se transforme en un equipo de alta performance?
Principios de escalabilidad
Basándonos en el libro “The art of scalability” de Abbot y Fisher, la escalabilidad de una aplicación se puede ilustrar como un cubo donde cada eje corresponde a una línea de escalabilidad. De esta forma existen tres tipos: Data Partitioning, Horizontal Duplication and Functional Decomposition.
- Data Partitioning: Corresponde a una división de datos que sean similares.
- Horizontal Duplication: Corresponde a escalar duplicando la aplicación. Un ejemplo de esto es google, el cual tiene su página web en distintos servidores a nivel mundial.
- Descomposición funcional: Corresponde a dividir toda lógica que sea distinta
Y es justamente la descomposición funcional una forma de reorganizar de forma rápida los proyectos que se están desarrollando. Pero ¿cómo se puede hacer esta descomposición y no morir en el intento?
Animales Microservicios fantasticos y donde encontrarlos
Hey tú! ¿Necesitas una descomposición funcional de tu app? Pues te traigo la solución y se llama Microservicios!. Puede sonar a un comercial de televisión, pero en cierta forma, los microservicios han cambiado la manera en que nos relacionamos con los productos. Estos nos ayudan a dividir de forma eficiente la lógica de nuestro proyecto y también permite una mejor comunicación entre los equipos.
Un microservicio es un componente de software independiente y deployeable que implementa alguna funcionalidad útil.
Así una mejor vista preliminar de cómo se vería la arquitectura de White Kitchens sería como en el diagrama. Este tipo de arquitecturas presentan distintos servicios con sus bases de datos separadas, conversando entre sí de forma óptima. De esta forma, los servicios trabajan en conjunto y si uno falla, el resto puede seguir funcionando. Además, la forma de organizar los servicios permite que los programadores se separen en team, generando pipelines separados para su deploy en producción. Pero una advertencia: encontrar los servicios correctos es desafiante ya que los sistemas distribuidos son complejos y decidir cuándo adoptarlos es difícil.
Existen 7 características que definen a los microservicios las cuál definen su funcionamiento:
- Each service has its own database
- Services are small and easily maintained
- Services are independently deployable
- Services are independently scalable
- It has better fault isolation
- The microservice architecture enables teams to be autonomous
- It allows easy experimenting and adoption of new technologies
Pero ¿cómo los diseñamos de forma exitosa?. Existen dos técnicas para definir microservicios. En primer lugar tenemos una división mediante las capacidades de la empresa, es decir, ver cómo se organiza la empresa y en base a ello descomponer sus funciones comunes. Una vez que tienes esto, se puede hacer un mapeo directo de los servicios a desarrollar. Aunque esta técnica no es la más recomendable dado que solo funciona en empresas grandes y tiene el riesgo de duplicar servicios en áreas distintas
Otra técnica muy usada es la descomposición mediante subdominios. Esta técnica plantea dividir los problemas que deberían ser abordados en el proyecto y encapsularlos en servicios únicos. De esta manera, en el ejemplo de la app Dark Kitchen es fácil notar que los sistemas de pagos y los sistemas de pedidos son problemas completamente distintos que pueden ser manejados en lógica diferente. Así, cuando se obtienen los sub dominios, es fácil realizar nuevamente el mapping a los servicios a desarrollar.
Con todo lo anterior es posible generar un pequeño framework de como crear tus microservicios.
- Genera un listado de todas las funciones que tu aplicación debe tener. Esto se puede hacer mediante design thinking o historias de usuario. La idea principal es que puedan obtener las funciones en un bajo nivel de abstracción. Por ejemplo, si tu aplicación obtiene una orden, entonces escribe en tu listado createOrder(). Luego si necesita validar la orden, entonces escribe validateOrder(). Por este motivo, intenta ser lo más exhaustivo posible.
- Agrupa las funciones que más se parecen. Por ejemplo, puedes crear un grupo Order para todas aquellas funciones que tengan relación directa con las órdenes de tu aplicación.
- Asigna los grupos a los subdominios que existen en tu proyecto. Esto te dará una visión inmediata de cuales son los microservicios a crear. Por ejemplo, el grupo Order vive en el subdominio de Order Management.
- Finalmente diseña la comunicación que tendrán estos servicios entre si. Pero eso lo podemos ver en otro post.
Con este overview tenemos una visión general de cómo se puede escalar una aplicación mediante su disposición funcional y con la ayuda de microservicios. Esperamos que esto sea de utilidad si estás iniciando un nuevo proyecto o si te sientes identificado con las aplicaciones monolíticas. Los microservicios están cambiando la forma de desarrollar proyectos y es bueno entenderlos.