El lado oscuro de los gestores de paquetes

Hace no tanto tiempo (aunque en la escala de internet pueda parecer siglos) gestionar las dependencias de un proyecto era algo relativamente complicado o, al menos, laborioso.

En aquellos lejanos y oscuros tiempos, para incluir en tu proyecto, por ejemplo, NHibernate, debías buscar la página de descarga correspondiente, elegir la versión correcta para tu versión de .NET, buscar posibles dependencias (log4net, Castle.DynamicProxy, etc.), cruzar los dedos para que estuvieran en el zip que te habías descargado de la página de NHibernate y, si no estaban, empezar a buscarlos en sus respectivas páginas de descarga, asegurándote de bajar las versiones exactas que necesitabas para NHibernate y rezando para que no hubiera colisiones entre unas versiones y otras, especialmente si había algún assembly firmado (recuerdo con especial «cariño» log4net y sus versiones 1.2.9 y 1.2.10). Finalmente copiabas todo con mucho cuidado en una carpeta .\lib al lado de los fuentes de tu proyecto y lo añadías a tu sistema de control de código fuente (subversion si tenías suerte, Visual Source Safe si no). Vamos, todo muy rápido, divertido y agradable.

Por suerte hoy en día prácticamente todas las plataformmas de desarrollo cuentan con gestores de paquetes para realizar esta gestión de dependencias y, lo que es más importante, se han convertido en la manera «oficial» y habitual de instalarlas. En realidad todo esto de los gestores de paquetes no es algo nuevo, existen desde hace mucho tiempo, pero en algunas plataformas como .NET su popularidad es relativamente reciente (que Microsoft publicase NuGet fue clave en una comunidad que había ignorado antes intentos como OpenWrap o Horn).

Utilizando un gestor de paquetes, todo el proceso de descarga e instalación de una dependencia que describía antes se reduce a algo como:

npm install underscore

Así de simple. Con sólo un comando el gestor de paquetes se conectará a un servidor que contiene un índice con los paquetes publicados y se descargará el paquete seleccionado, ejecutará los scripts de instalación adecuados y hará lo mismo recursivamente con las dependencias del paquete indicado.

Además, estos paquetes que has instalado quedan registrados en un fichero de configuración asociado al proyecto, por lo que existe un punto centralizado en el que revisar qué dependencias tienes instaladas y con qué versiones.

Visto así, es innegable que los gestores de paquetes suponen un gran avance para el desarrollo de software facilitando la gestión de dependencias y seguramente sean uno de los motivos por los cuales hoy en día se utilizan cada vez más componentes externos (muchos de ellos open source) en todo tipo de proyectos.

Cuidado con las dependencias que asumes

Desgraciadamente esta facilidad de uso también tiene puede tener consecuencias negativas y es importante tenerlas en cuenta.

Al ser tan sencillo añadir nuevas dependencias a un proyecto, es habitual encontrar proyectos con decenas de dependencias externas, cada una de ellas con sus propias dependencias. Esto no es algo malo en si mismo, a fin de cuentas, ¿para qué reinventar la rueda?, pero no hay que olvidar que cada dependencia que asumes en un proyecto es una dependencia que tienes que mantener.

Es tan fácil añadir un nuevo paquete que a veces, en lugar de implementar una función de 10 líneas de código, se acaba asumiendo una dependencia sobre una librería de miles de líneas de código para usar sólo esa función. El utilizar una librería externa, probada y ampliamente usada proporciona una sensación de seguridad que no siempre es real y, cuando se produce un problema, bien porque hay un bug en esa librería o porque hay problemas de compatibilidad entre dos librerías que estamos utilizando, depurarlo puede ser bastante complicado.

Cuando aparece un problema, no sirve de nada decir «el problema no está en mi código sino en la versión 1.9.10 de jQuery, yo no tengo la culpa». De cara a tus usuarios y tus clientes, lo que no funciona es tu aplicacion, esa por la que ellos han pagado. Al final tienes que responder no sólo por tu código sino por el de todas las dependencias que estás utilizando.

Esto no quiere decir que no haya que utilizar librerías externas. Igual que no tiene mucho sentido ponerte a escribir tu propio servidor web y es más razonable usar IIS, Apache o lo que más te guste, no merece la pena invertir tiempo en escribir tu propio ORM cuando hay alternativas perfectamente válidas.

Lo que sí es necesario es ser consciente de que cada dependencia que asumes tiene un precio. Cuando programamos, si vemos una clase que recibe 12 parámetros en el constructor lo consideramos como algo problemático (o al menos algo que requiere cierta justificación), porque sabemos que esa clase tiene demasiadas dependencias, lo que aumenta su acoplamiento con el resto del sistema y complica su futuro mantenimento. Pues con las dependencias de los proyectos pasa algo parecido. Si un proyecto depende de 12 paquetes distintos, estamos introduciendo 12 puntos de problemas potenciales, por lo que más nos vale ser conscientes de ellos y tener un buen motivo para hacerlo.

Recuperación automática de paquetes

Todos los gestores de paquetes que conozco cuentan con alguna opción para volver a descargarse los paquetes desde un repositorio central. Si recordáis el flujo de trabajo que describía al principio del post, antes era frecuente almacenar los paquetes en alguna carpeta de proyecto e incluirlos en el control de código fuente junto con nuestro propio código. Hoy en día, esto no es lo habitual e incluso se considera una mala práctica, puesto que podemos incluir únicamente un fichero de configuración y descargar los paquetes bajo demanda de sus servidores de origen cuando los necesitemos.

La principal ventaja de esto es que el tamaño de nuestro repositorio de código será mucho menor, especialmente si los paquetes se distribuyen de forma binaria (como es habitual en .NET o Java), que es más difícil de comprimir y de almacenar usando deltas cuando hay cambios de versión.

En un mundo en el que el almacenamiento en disco es tan barato puede parecer que esto no es importante, pero cuando pensamos en el tráfico de red al clonar un repositorio, especialmente con sistemas de control de versiones distribuidos como Git o Mercurial en los que se clona el repositorio completo (y no sólo una versión), y en accesos a través de conexiones a internet en lugar de redes locales, sí que es un factor a tener en cuenta.

Como siempre, esto no es gratis y utilizar la recuperación automática de paquetes en lugar de almacenar las dependencias en el control de código fuente también tiene sus inconvenientes. Ahora, para poder compilar nuestro proyecto, dependemos de que el repositorio de paquetes esté disponible. Por suerte no es muy habitual que estos repositorios centrales se caigan, pero tampoco es imposible.

Esto se puede resolver creando un repositorio de paquetes propio, dentro de tu red local, con una copia de los paquetes que usas en tus proyectos. Es una buena opción, especialmente en organizaciones grandes donde compensa aprovechar las ventajas de no incluir las dependencias en el control de código fuente y se necesitan unas ciertas garantías de disponibilidad, pero montar estos servidores requiere algo de tiempo y es una pieza más del sistema a mantener.

Personalmente, cuando estoy desarrollando un proyecto profesionalmente, siempre incluyo todas las dependencias en el control de código fuente. En mi escenario concreto, con todos los desarrolladores trabajando en una misma red local, no me compensa el riesgo de tener que lidiar con un servidor externo caído o un paquete eliminado para ahorrarme 10 segundos al clonar un repositorio (operación que, por otra parte, tampoco es muy frecuente en nuestro caso).

Conclusiones

Los gestores de paquetes son un gran invento para facilitarnos las cosas al desarrollar aplicaciones porque nos permiten gestionar las dependencias de una manera fácil y cómoda, pero eso no debe hacernos olvidar que cada paquete que instalamos es una dependencia de nuestra aplicación por la que deberemos responder en el futuro.

La existencia de servidores centralizados desde los que descargar las dependencias simplifica el acceso a las mismas y puedo ayudarnos a reducir el almacenamiento en disco y el tráfico de red, aunque a cambio estaremos introduciendo una dependencia entre nuestros procesos y la disponibilidad de esos servidores, por lo que será necesario valorar si realmente merece la pena.

4 comentarios en “El lado oscuro de los gestores de paquetes

  1. Para mis desarrollos siempre incluyo las librerías en una carpeta «lib» que subo al sistema de control de código fuente. NuGet es cómodo pero sólo lo uso para desarrollos rápidos en los que quiero probar algo. Es la opción más razonable, de hecho si alguien interesado en el código de aplicaciones de código abierto se descarga los repositorios de los proyectos más populares verá que todos tienen su carpeta «lib» con las dependencias.

    Señor Hernández, estaría bien que en alguna ocasión nos indicara cómo estructura las carpetas de sus proyectos. En mi caso suele ser así:

    >> build
    >> docs
    >> lib
    >> samples
    >> src
    >> tests
    >> tools
    > .gitignore

    – build: «scripts» para la integración continua.
    – tests: normalmente las pruebas las incluyo en la carpeta «src» (código fuente) pero puede haber herramientas que necesiten crear proyectos de pruebas y las incluyo en «tests».

  2. Hola,

    La estructura de carpeta depende un poco de la plataforma que utilice (no lo organizo igual en javascript que en .NET, por ejemplo), pero en .NET suelo usar algo casi idéntico a lo que tu planteas:

    • build: resultado de la compilación (excluida del DVCS)
    • doc: documentación generada en el proyecto (manuales, guías de instalación, etc.)
    • lib: dependencias de la aplicación (si las gestiono manualmente, si uso un gestor de paquetes, sigo su convención)
    • msbuild: ficheros de compilación
    • reports: resultados de tests, logs, etc. (excluida del DVCS)
    • src: fuentes. Puede incluir varias soluciones de visual studio y cada solución varios proyectos, incluyendo los proyectos de tests
    • tools: herramientas varias para generar el proyecto (generadores de docs, test runners, etc.)

    La idea es que si te bajas el código del repositorio puedas compilarlo sin necesidad de instalarte nada más que el SDK del .NET y que todas las herramientas se puedan ejecutar por línea de comandos para lanzarlas desde el script de compilación automáticamente.

    Un saludo,

    Juanma.

  3. Pingback: De NuGet y la gestión de paquetes - Burbujas en .NET

Comentarios cerrados.