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.
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.
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.
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.
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:
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.
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.
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.
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.
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: