====== Microservicios ====== Los métodos ágiles proponen iteraciones rápidas, con entregas frecuentes de nuevas versiones para obtener retroalimentación y, si es necesario, realizar cambios de dirección. Sin embargo, incluso si una empresa adopta un método ágil, como Scrum, enfrentará un cuello de botella arquitectónico cuando necesite lanzar nuevas versiones de un producto de forma frecuente. Este cuello de botella ocurre porque los sistemas, en general, siguen una arquitectura monolítica en tiempo de ejecución. Es decir, aunque el desarrollo haya sido particionado en módulos ''M1'', ''M2'', ''M3'', …, ''Mn'', en tiempo de ejecución estos módulos se ejecutan como un único proceso en el sistema operativo. Así, todos los módulos comparten el mismo espacio de direcciones. En otras palabras, en tiempo de ejecución el sistema es un gran monolítico, como ilustra la siguiente figura. {{ :wiki:monolitico.png?250 |}} En un monolítico, siempre existe el riesgo de que un cambio realizado por un equipo ''T'' en un módulo ''Mi'' cause un efecto secundario en un módulo ''Mj''. Por ejemplo, ''Mi'' y ''Mj'' pueden compartir una variable global o un atributo estático. Un cambio en esta variable, realizado en ''Mi'', podría comprometer el funcionamiento de ''Mj''. En realidad, este riesgo es mayor de lo que un lector principiante en desarrollo de sistemas podría imaginar. Para evitar que los clientes se encuentren con errores inesperados en sus sistemas, las empresas que utilizan arquitecturas monolíticas adoptan un proceso riguroso y burocrático para el lanzamiento de nuevas versiones. Este proceso puede incluir incluso pruebas manuales antes de liberar el sistema para producción. Por pruebas manuales, nos referimos a un evaluador que utiliza las funcionalidades más críticas del sistema para simular una sesión de uso de un cliente final. Para superar este cuello de botella —donde el desarrollo se ha vuelto ágil, pero la puesta en producción sigue siendo burocrática— recientemente algunas empresas han comenzado a migrar sus monolíticos a una arquitectura basada en microservicios. La idea es simple: ciertos grupos de módulos se ejecutan en procesos independientes, sin compartir memoria. Es decir, el sistema se descompone en módulos, no solo durante el desarrollo, sino también en tiempo de ejecución. Con esto, las posibilidades de que los cambios en un módulo causen problemas en otros se reducen considerablemente. Cuando los módulos están separados en procesos distintos, ya no es posible que un módulo acceda a un recurso interno de otro módulo, como una variable global, un atributo estático o una interfaz interna. En su lugar, toda comunicación debe ocurrir a través de las interfaces públicas de los módulos. Así, los microservicios aseguran que los equipos de desarrollo solo utilicen interfaces públicas de otros sistemas, y esta regla es garantizada por el sistema operativo. La siguiente figura muestra una versión basada en microservicios de nuestro ejemplo. En ella, todavía existen nueve módulos, pero se ejecutan en seis procesos independientes, representados por los cuadros o rectángulos que rodean los módulos. Los módulos ''M1'', ''M2'', ''M3'' y ''M6'' se ejecutan, cada uno, en un proceso independiente. Los módulos ''M4'' y ''M5'' se ejecutan en un quinto proceso. Finalmente, los módulos ''M7'', ''M8'' y ''M9'' se ejecutan juntos en un sexto proceso. {{ :wiki:micro_2.png?250 |}} Hasta este punto de la explicación, hemos usado el término proceso, aunque el nombre del patrón se refiere a ellos como servicios. Además, los servicios son micro porque no implementan funcionalidades complejas. Recuerde que son desarrollados por equipos ágiles, que, como mencionamos en el Capítulo 2, son equipos pequeños, de alrededor de cinco desarrolladores, por ejemplo. En consecuencia, los equipos pequeños no tienen la capacidad de implementar servicios grandes. Una segunda ventaja de los microservicios es la escalabilidad. Cuando un monolítico enfrenta problemas de rendimiento, una solución consiste en desplegar instancias del sistema en diferentes máquinas, como muestra la siguiente figura. Esta solución se llama escalabilidad horizontal. Por ejemplo, permite dividir a los clientes del sistema entre las dos instancias que se muestran en la figura. Como se trata de un monolítico, ambas instancias son idénticas, es decir, tienen los mismos módulos. {{ :wiki:micro_3.png?400 |}} Sin embargo, los problemas de rendimiento pueden deberse a servicios específicos, como el servicio de autenticación de usuarios. Entonces, los microservicios permiten replicar solo los componentes directamente relacionados con esos problemas de rendimiento. La figura siguiente muestra una nueva instalación de nuestro sistema basado en microservicios. {{ :wiki:micro_4.png?400 |}} El segundo servidor disponible incluye solo instancias del servicio ''M1'', con la suposición de que ''M1'' es responsable de la mayoría de los problemas de rendimiento de la instalación inicial. En la primera instalación, teníamos una única instancia de M1. Ahora tenemos seis instancias, todas ellas ejecutándose en un nuevo servidor. Hasta el momento, hemos enumerado dos ventajas de los microservicios: (1) permiten la evolución más rápida e independiente de un sistema, permitiendo que cada equipo tenga su propio régimen de liberación de nuevas versiones; (2) permiten escalar un sistema a un nivel de granularidad más fino que en los monolíticos. Pero existen al menos dos ventajas más: * Dado que los microservicios son autónomos e independientes, pueden implementarse en diferentes tecnologías, incluidas lenguajes de programación, frameworks y bases de datos. Por ejemplo, el microservicio de registro de clientes en un sistema de comercio electrónico puede implementarse en Java con una base de datos relacional. Mientras tanto, el microservicio de recomendación de compras podría implementarse en Python con una base de datos NoSQL. * En un monolítico, las fallas son totales. Si la base de datos falla, todos los servicios quedan fuera de servicio. En cambio, en arquitecturas basadas en microservicios, podemos tener fallas parciales. Por ejemplo, supongamos que el microservicio de recomendaciones de compras del ejemplo anterior queda fuera de servicio. Los clientes aún podrán buscar productos, realizar compras, etc., pero verán un mensaje en el área de recomendaciones que indica que estas están deshabilitadas; o bien, esta área quedará vacía o no se mostrará a los usuarios mientras el microservicio de recomendaciones esté fuera de servicio. Las arquitecturas basadas en microservicios se han vuelto posibles gracias a la aparición de plataformas de computación en la nube. Con estas plataformas, las empresas ya no necesitan comprar y mantener hardware y software básico, como sistemas operativos, bases de datos y servidores web. En su lugar, pueden alquilar una máquina virtual en una plataforma de computación en la nube y pagar por hora de uso. Esto facilita escalar un microservicio horizontalmente, añadiendo nuevas máquinas virtuales. **Profundización**: Los microservicios constituyen un ejemplo de aplicación de la Ley de Conway. Formulada en 1968 por Melvin Conway, esta es una de las leyes empíricas sobre desarrollo de software, al igual que la Ley de Brooks. La Ley de Conway afirma lo siguiente: las empresas tienden a adoptar arquitecturas de software que son copias de sus estructuras organizacionales. En otras palabras, la arquitectura de los sistemas de una empresa tiende a reflejar su organigrama. Por eso, no es coincidencia que los microservicios sean utilizados principalmente por grandes empresas de Internet que tienen cientos de equipos de desarrollo distribuidos en diversos países. Estos equipos, además de estar descentralizados, son autónomos y siempre están incentivados a producir innovaciones. ===== Administración de Datos ===== Al menos en su forma pura, los microservicios deben ser autónomos también desde el punto de vista de los datos. Es decir, deben gestionar los datos que necesitan para proporcionar su servicio. Por lo tanto, el escenario ilustrado en la siguiente figura — en el que dos microservicios comparten la misma base de datos — no es recomendable en una arquitectura basada en microservicios. {{ :wiki:micro_5.png?200 |}} Lo ideal es que ''M1'' y ''M2'' sean independientes también desde el punto de vista de las bases de datos, como se muestra en la siguiente figura. La razón principal es que tener una única base de datos también puede convertirse en un obstáculo para la evolución del sistema. {{ :wiki:micro_6.png?200 |}} Por ejemplo, en equipos y arquitecturas de desarrollo tradicionales suele haber un administrador de datos, responsable de gestionar las tablas de la base de datos. Cualquier cambio en la base de datos — como la creación de una columna en una tabla — necesita la aprobación del administrador de datos. Esta autoridad central tiene que conciliar los intereses, muchas veces conflictivos, de los distintos equipos de desarrollo. Por ello, sus decisiones pueden volverse lentas y burocráticas, retrasando la evolución del sistema. ===== ¿Cuando no usar Microservicios ===== Hasta este punto, hemos presentado las ventajas y beneficios de los microservicios. Pero es importante señalar que esta arquitectura es más compleja que una arquitectura monolítica. Esto se debe a que los microservicios son procesos independientes, lo que, por diseño, da lugar a sistemas distribuidos. Por lo tanto, al usar microservicios, debemos enfrentar todos los desafíos que aparecen al implementar un sistema distribuido. Entre ellos, podemos mencionar: * Complejidad: cuando dos módulos se ejecutan en el mismo proceso, la comunicación entre ellos se realiza mediante llamadas a métodos. Cuando estos módulos están en máquinas diferentes, la comunicación entre ellos debe usar algún protocolo de comunicación, como HTTP/REST. Es decir, los desarrolladores tendrán que dominar y utilizar un conjunto de tecnologías para la comunicación en redes. * Latencia: la comunicación entre microservicios también implica una mayor demora, conocida como latencia. Cuando un cliente llama a un método en un sistema monolítico, la latencia es mínima. Por ejemplo, rara vez un desarrollador dejará de usar una llamada de método solo para mejorar el rendimiento de su sistema. Sin embargo, este escenario cambia cuando el servicio llamado está en otra máquina, quizás al otro lado del mundo en el caso de una empresa global. En tales situaciones, existe un costo de comunicación que no es insignificante. Cualquiera que sea el protocolo de comunicación utilizado, esta llamada tendrá que atravesar el cable de la red — o por el aire y la fibra óptica — hasta llegar a la máquina de destino. * Transacciones Distribuidas: Como hemos visto, los microservicios deben ser autónomos también desde el punto de vista de los datos. Esto hace más complejo garantizar que las operaciones que actúan sobre dos o más bases de datos sean atómicas, es decir, que se ejecuten con éxito en todas las bases o que fallen en todas. Supongamos, por ejemplo, dos microservicios de pago con tarjeta de crédito, a los que llamaremos X e Y. Supongamos que una tienda en línea permite dividir el valor de una compra entre dos tarjetas. Por ejemplo, una compra de US$ 2,000.00 puede pagarse debitando US$ 1,500.00 de la tarjeta X y US$ 500.00 de la tarjeta Y. Sin embargo, estas transacciones deben ser atómicas: o ambas tarjetas son debitadas o ninguna lo es. Por eso, en arquitecturas basadas en microservicios, pueden ser necesarios protocolos de transacciones distribuidas, como el two-phase commit, para garantizar una semántica de transacción en operaciones que escriben en más de una base de datos.