Posted: 18 Feb. 2021 5 Tiempo de lectura

Gestión de código fuente con repositorios monolíticos (monorepos)

¡Las arquitecturas de microservicios han llegado para quedarse! Esta técnica de estructuración de los sistemas de software, cuyo término fue mencionado por primera vez en una conferencia del Dr. Peter Rogers en el 2005, y popularizada seis años más tarde tras la implantación de Netflix sobre AWS, es de las más populares a día de hoy, sobre todo aquellas implantadas en Cloud a nivel global.

Los microservicios son una evolución de la ya antigua arquitectura SOA (Service-oriented architecture), pero con un nivel de desacoplamiento casi extremo. Todos los servicios se desarrollan, despliegan y ejecutan de manera independiente, incluso a nivel de runtime de ejecución, bases de datos, número de instancias, etc. La separación en microservicios permite a los arquitectos de software seguir una estrategia de divide&conquer mucho más eficiente, y esto se nota principalmente en aplicaciones de alta complejidad.

Hasta aquí, la teoría. Porque como todo en la vida, los microservicios también tienen un lado práctico. Práctico y oscuro.

No me voy a detener en temas como lo complejo que puede ser comunicar y orquestar microservicios o los aumentos de latencia que se producen debido a flujos de ejecución limpios pero ineficientes. Este artículo pretende centrarse en algo mucho más terrenal para los desarrolladores.

Supongamos que tenemos el siguiente escenario:

Desarrollamos un proyecto que consta (entre otras cosas) de un grupo de aplicaciones desplegado como microservicios. Todos estos microservicios utilizan el mismo lenguaje y framework de desarrollo y comparten base de datos, entidades de negocio, logs, etc.

Si nos ceñimos de manera estricta a la doctrina, cada microservicio debería desarrollarse de manera totalmente independiente, aunque en este caso compartan lenguaje, DTDs, claves, librerías de acceso, etc.

En estos casos donde queremos mantener la estructura de nuestros servicios y evitar el mantenimiento exponencial, es donde un monorepo puede tener sentido.

Pero, ¿qué es un monorepo? Es una forma de estructurar nuestro código fuente de diferentes proyectos (aplicaciones, servicios) en un único repositorio de código fuente. Este sistema no es especialmente novedoso, pero se ha popularizado en los últimos años a partir del uso que han hecho de él compañías como Google, Facebook, Microsoft o Uber para estructurar y gestionar el código fuente de sus propias aplicaciones.

Como ventajas de usar monorepos tenemos que los elementos comunes y repetidos en todos los servicios se pueden separar en librerías compartidas; la descarga de librerías de terceros se realiza una sola vez y las actualizaciones se aplican sobre todos los servicios de manera automática; y si es necesario realizar cambios, estos se pueden aplicar a todo el proyecto, ya que los desarrolladores tienen acceso.

Como principales desventajas tenemos: necesidad de repositorios de mayor tamaño o dificultad para gestionar los controles de acceso de los desarrolladores.

Importante: los monorepos son válidos en cualquier arquitectura, ¡no solo de microservicios!

Ejemplo práctico

Existen múltiples librerías y frameworks para gestionar proyectos en modo monorepo. Lerna es una ellas. Permite la gestión de múltiples paquetes Javascript en un solo repositorio.

Para instalar lerna, ejecutamos:

npm install -g lerna

Luego creamos el directorio para nuestro proyecto y lo inicializamos:

mkdir mi-proyecto

cd  mi-proyecto

lerna init

Con lerna init se crea una estructura de ficheros como esta:

mi-proyecto/

  packages/

  package.json

  lerna.json

Si no especificamos nada, se inicializa en modo fijo (fixed). En este modo los proyectos operan en una sola línea de versión. La versión se mantiene en el archivo lerna.json en la raíz. Cuando se ejecuta lerna version, si un módulo se ha actualizado desde la última release, se actualizará a la nueva versión que se está publicando. En otras palabras, solo publica una nueva versión de un paquete cuando lo necesita.

En cambio, si especificamos el modo de esta manera lerna init --independent, las versiones de los paquetes se gestionan de manera independiente. Cada vez que publica, aparece un mensaje para cada paquete modificado donde se debe especificar si se trata de un parche, un cambio menor, mayor, etc.

A modo de ejemplo, creamos dos paquetes de la siguiente forma:

lerna create test1

lerna create test2

Al ejecutar este comando, se realizan una serie de preguntas, como la versión, palabras clave, punto de entrada, etc. para luego crear la estructura base y el fichero package.json correspondiente. A partir de este punto ya podemos empezar a trabajar con nuestros paquetes de manera habitual.

Si necesitamos agregar una dependencia entre dos paquetes (por ejemplo que test1 utilice test2), podemos hacerlo de la siguiente manera:

packages\test1\lib\test1.js

Como veis, el require se hace directamente sobre el nombre del paquete, y no sobre el path. Si intentamos ejecutar este paquete en este punto mediante node .\packages\test1\lib\test1.js nos daría un error de dependencias.

Para solucionarlo, tenemos que agregar el paquete test2 como dependencia en el fichero package.json de test1:

packages\test1\package.json

Luego, ejecutamos lerna bootstrap para que agregue la dependencia en node_modules. Este comando realiza los mapeos de dependencias necesarios para que todo funcione.

Si volvemos a ejecutar node .\packages\test1\lib\test1.js, ya no se produce el error.

Lerna también cuenta con un par de comandos interesantes más:

lerna add: agrega una librería externa al repositorio. Si no se especifica ningún paquete, lo hace para todos los paquetes.

lerna version: genera una nueva versión de uno o más paquetes, y actualiza las dependencias correspondientes.

La documentación completa, con todos los comandos y opciones, está disponible aquí.

Como conclusión, la estrategia de almacenaje del código fuente es una decisión de calado, y hay que evaluar lo máximo posible antes de ir por uno u otro camino. Cuando tengáis que montar un proyecto desde cero, os animo a organizar vuestros proyectos con la estrategia monorepo con el lenguaje de vuestra preferencia.

 

Conoce a nuestro experto

Adrián García Pera, senior specialist de Consultoría Tecnológica de Deloitte