Introducción
Tanto a la hora de crear un nuevo proyecto, como de migrar uno existente en el roadmap de un cambio de arquitectura, una pregunta que genera dudas y confusión en los equipos es sobre la necesidad de utilizar un monorepositorio o un multirepositorio, algo que suele decantarse por criterios como creer en lo moderna de una de las opciones o en la -no existente- asociación entre arquitectura propia del proyecto y la forma de organizar el código, así como la experiencia en el uso de repositorios que se haya dado para los circuitos de integración continua.
– Oye, pero no sabías que Facebook utiliza un monorepo?
– What? Pero cómo van a tener una estructura semejante para tanto código? No conocen los problemas y anticuado de la solución? Serán los únicos!
– También lo hace Google, e infinidad de empresas que manejan enormes cantidades de código.
– Pues nosotros usaremos multirepo, queremos ir a SaaS y tenemos múltiples equipos.
– Eso no son condiciones suficientes para tomar esa decisión.
Con la implantación de los principios SOLID y DRY, mejoras en herramientas de integración continua, repositorios GIT y metodologías de trabajo en equipos grandes y divididos, aumentaron rápidamente los proyectos que adaptaron la forma de trabajar con multirepositorios, asignando personas con permisos a estos repositorios según en qué capa, tecnología o responsabilidad se encontraban. El uso de microservicios y la orientación SaaS también ha fomentado la división de repositorios. ¿Pero, un monorepositorio implica tener un monolito?
Monolito vs Componentes
Una aplicación con estructura monolítica, es aquella en la que no existe separación de componentes o separación de capas. Por contra, en una estructura basada en componentes (como microservicios), existe una separación entre componentes, servicios o capas de la aplicación.
Pero no todo es blanco o negro. Podemos tener aplicaciones que no sean un único monolito, o que no estén completamente divididas entre capas y componentes, sino algo entre medias. Por ejemplo un monolito de la capa frontend, y una aplicación de microservicios en la capa backend, o una aplicación frontend con web components, y una aplicación backend monolítica.
Organización de tu proyecto
Entonces, los repositorios son la forma en la que organizamos nuestro código. Dada la casuística de nuestra aplicación, podremos decidir si nuestra aplicación tiende al monolito o a la división, decidir si alojar su código en:
monorepo -> monolito
monorepo -> componentes
multirepo -> componentes
Algunos ejemplos de división de nuestra aplicación, pueden ser:
Veamos las ventajas e inconvenientes que tiene el uso de multi repositorios, y a continuación, las implicaciones que tiene el uso de mono repositorios.
Ventajas multirepo
Clonado independiente
No hace falta descargarse todo el proyecto para trabajar, sino clonarse únicamente las partes que se necesiten para trabajar. Esto disminuye la complejidad de un gran proyecto a la hora de utilizar la funcionalidad del controlador de versiones.
Gestión de permisos y atomización de la responsabilidad
Pueden asignarse permisos por repositorio. Únicamente las personas involucradas en cada uno de ellos tendrán los permisos correspondientes para ver o modificar el código. De esta forma, solo tienes permisos de actuación sobre tu ámbito de responsabilidad.
Evolución propia en versionado
Cada componente puede evolucionar de forma independiente, dando libertad a los equipos para continuar su trabajo de una forma más aislada, lo que proporciona que puedan tomar decisiones sobre su biblioteca sin tener que esperar o tomar decisiones en conjunto.
Puede construírse una pieza propia en el circuito de integración continua, aportando su propio ciclo de vida de desarrollo y pruebas.
Reutilización del código
El uso de librerías independientes es fácilmente reutilizable, tanto por componentes del propio proyecto, como por otros proyectos.
Por ejemplo, puedes tener en un repositorio un componente de arquitectura o una utilidad que sea reutilizada en varios proyectos, incluso migrando su código a otro repositorio.
Desventajas multirepo
Pérdida de visión global
Si tu ámbito abarca solo algunas partes del proyecto, o una parte pequeña, pierdes la visión de foco de objetivos globales, así como el conocimiento funcional de parte de la aplicación. La capacidad de saber priorizar tareas también se ve afectada, dado que sólo tienes el conocimiento relativo a la parte con la que trabajas.
Dificultad de introducción al proyecto
Para alguien nuevo en el proyecto, tener que montar todas las piezas necesarias para funcionar en local, puede suponer un dolor si lo comparamos con la opción de tener todo descargado, visible y funcional mediante una única descarga.
División de equipos
Cuanto más divididos estén los equipos, más tratarán de alcanzar sus objetivos propios, viéndose afectada la priorización global de las tareas.
La fiesta de las dependencias
La evolución en versionado independiente de las librerías, hace que los componentes tengan que estar en constante alerta ante la necesidad de adaptarse a nuevas evoluciones. Esto muchas veces termina en componentes que se quedan sin actualizar utilizando dependencias antiguas, o peor aun, en conflictos entre versiones de dependencias y en el problema del diamante. En general, mantener un sistema con muchos artefactos versionados, añade mucha complejidad a las capas.
Cambios cross-proyect
Los equipos necesitan actualizar constantemente sus dependencias y conocer el estado en el que están el resto, lo cual añade complejidad al sistema, ya que no existe visibilidad sobre el resto. La mayor parte de las veces, implicará una complicada gestión manual entre los distintos equipos y/o la creación de scripts que informen, lo cual no termina con los problemas producidos.
Dado que cada equipo puede avanzar con una velocidad distinta, se corre el riesgo de acabar utilizando versiones que no están actualizadas pero deberían estarlo.
Ventajas monorepo
A partir de las desventajas del uso del multirepo, pueden suponerse las ventajas que otorga el uso de monorepo.
Versionado unificado y única fuente de verdad
Una organización centralizada, implica que los equipos tengan una facilidad mayor al introducirse y montar el proyecto, así como una visión funcional más amplia del proyecto. Un único repositorio proporciona un control de versiones unificado y una única fuente de verdad. No hay confusión sobre qué repositorio aloja la versión autorizada de un archivo. Si un equipo quiere depender del código de otro equipo, puede depender de él directamente.
Reutilización de código
Contener una cantidad considerable de librerías útiles en un mismo lugar, conduce a que se produzca un mayor intercambio de conocimiento, así como a una mayor reutilización del código.
Gestión de dependencias simplificada
La gestión de dependencias puede fácilmente simplificarse, agrupando versionado común de artefactos o proyectos.
La actualización de las distintas funcionalidades del proyecto, también es mucho mejor, lo que ayuda a detectar inmediatamente actualizaciones que haya que llevar a cabo en tu librería en el proyecto con respecto a los avances que han hecho otros equipos. Existen herramientas que pueden desencadenar una reconstrucción del código dependiente. Evitamos el problema del diamante de dependencias, dado que existe una única fuente de verdad y desaparece el problema por el control de versiones independiente de las dependencias.
El problema del diamante ocurre cuando A depende de B y C, tanto B como C dependen de D, pero B requiere la versión D.1 y C requiere la versión D.2. En la mayoría de los casos, ahora es imposible compilar A. Para la biblioteca base D, puede resultar muy difícil lanzar una nueva versión sin causar roturas, ya que todos los componentes que la utilizan deben actualizarse al mismo tiempo. La actualización es difícil cuando los componentes que llaman a la biblioteca están alojados en diferentes repositorios.
Tip! Mediación de dependencias en Maven: cuando Maven se encuentra con varias versiones de una misma dependencia, usa la versión de la dependencia más cercana a su proyecto en el árbol de dependencias. Se puede garantizar una versión declarándola explícitamente en el POM de su proyecto. Si las dependencias son transitivas, si dos versiones de dependencia están a la misma profundidad en el árbol de dependencia, Maven utilizará la primera que haya sido declarada.
Con la utilización de un monorepo, el cambio producido por una dependencia, es inmediatamente propagado a través del árbol a todos los productos que la utilicen, de forma que es mucho más fácil para la persona encargada actualizar todo el código implicado, no acumulando deuda técnica para cuando los problemas aparezcan en el futuro.
Cambios atómicos
La capacidad de realizar cambios atómicos también es una característica muy poderosa del modelo monolítico. En refactorizaciones de larga escala, un desarrollador puede realizar un cambio importante al tocar cientos o miles de archivos en el repositorio en una sola operación. Por ejemplo, un desarrollador puede cambiar el nombre de una clase o función en una única confirmación y, sin embargo, no romper ninguna compilación o prueba.
Para cambios mayores, como pueden ser el aumento de versión de un framework, una persona o equipo pueden igualmente encargarse de llevar a cabo la refactorización de forma más eficaz, ya que al tener el árbol completo en el repositorio, los cambios que deben realizarse por rotura de código, pueden localizarse fácilmente.
Visibilidad del código y colaboración entre equipos
Otro atributo de un repositorio monolítico es que el diseño del código base se comprende mejor, ya que está organizado en un solo árbol. Las personas involucradas en cada capa o componente del código tienen una visión general del proyecto, saben dónde se encuentran y pueden de una forma más cómoda colaborar, cambiar los límites de su ámbito de acceso a capas/componentes, o moverse entre equipos distintos.
Desventajas monorepo
Tooling
El uso de un monorepo implica tooling para la gestión de este. Para realizar el circuito de integración continua, las herramientas más conocidas son suficientes, pero es necesario realizar una configuración especial, ya que no quieres hacer la build y el despliegue de toas las partes que componen el proyecto. Cargar un proyecto grande en tu IDE puede resultar pesado, si bien existen herramientas especiales para el manejo de este.
Mantenimiento del código
Si bien es más fácil detectar y realizar las actualizaciones que se requieran y de deuda técnica, la obligación de mantener todo tu proyecto a la última, requiere de un mayor esfuerzo constante, aunque luego se vea compensado.
Utilizar ramas para tareas de larga duración, implica enfrentarse a un esfuerzo de merge y actualización de la rama principal mayor, dadas las muchas evoluciones que ha podido tener la rama principal de desarrollo.
Conclusión
A la hora de elegir que tipo de repositorio utilizaremos, deberá consensuarse una opción entre el equipo de arquitectura del proyecto.
Grandes empresas como Google, Facebook, Microsoft, Uber, Airbnb o Twitter utilizan monorepo, si bien han adoptado todo el stack necesario y entrenado a sus equipos para ello.
En opinión de la propia Google,
“El modelo monolítico de gestión del código fuente no es para todos. Se adapta mejor a organizaciones como Google, con una cultura abierta y colaborativa. No funcionaría bien para organizaciones donde gran parte del código base es privado u oculto entre grupos. En Google, hemos descubierto que, con algo de inversión, el modelo monolítico de gestión de fuentes puede escalar con éxito a una base de código con más de mil millones de archivos, 35 millones de commits y miles de usuarios en todo el mundo.”