Close

Lidiar con las dependencias de Maven al cambiar a Git (en inglés)

Primer plano de Nicola Paolucci
Matt Shelton

Experto en desarrollo


Vamos a migrar a Git y nos encanta git-flow. ¿Y ahora qué? ¡Vamos a probarlo todo! Mi equipo es fantástico. Elaboraron una lista de los flujos de trabajo de los desarrolladores en Confluence, todos basados en lo que habíamos estado haciendo en equipo y en todas las cosas raras que pensaron que tendríamos que hacer en el futuro. Luego, en una estructura de proyecto que refleja la nuestra (pero sin incluir código, solo un pom.xml), probamos todos los flujos de trabajo.

Las dependencias de Maven estaban a punto de convertirse en nuestro mayor problema.

Numeración de compilaciones de Maven: Maven produce compilaciones 1.0.0-SNAPSHOT hasta que publicas. Cuando publicas, -SNAPSHOT se elimina y la versión pasa a ser 1.0.0. Tu proceso de compilación debe permitir que aumentes el número de versión menor después para que el trabajo posterior produzca compilaciones como 1.1.0-SNAPSHOT. No estás obligado a usar tres dígitos: puedes definir esto al empezar un proyecto, pero nosotros usamos tres. Con todo, es esencial que entiendas la parte -SNAPSHOT. Siempre representará la última publicación preliminar de un proyecto.

Artefactos


Nuestra gran preocupación en todos estos flujos de trabajo era cómo íbamos a asegurarnos de que las versiones de nuestros proyectos y las dependencias entre proyectos se gestionaran correctamente.

Cada vez que se recuperen dependencias de Maven para una compilación, de forma predeterminada se incorporan desde Internet. Esos artefactos se almacenan localmente para que las compilaciones posteriores se puedan realizar con mayor rapidez. Una solución para que esto sea un poco menos tedioso es usar un repositorio de artefactos en tu red local que sirva de memoria caché local para esas dependencias externas. La recuperación de LAN casi siempre será más rápida que la descarga, incluso desde las CDN más rápidas. Usamos Artifactory Pro como repositorio de artefactos. Además, al tener una estructura de varios módulos, también almacenamos nuestros propios artefactos de compilación en Artifactory. Cuando compilamos uno de nuestros paquetes comunes, podemos incorporar esa versión específica a través de la resolución de dependencias de Maven y recuperar el artefacto directamente del repositorio de artefactos.

Funciona a las mil maravillas. Artifactory también permite sincronizar los artefactos entre instancias, por lo que si quieres, por ejemplo, usarlo para replicar tu repositorio de publicación en tus centros de datos para implementaciones de producción, puedes hacerlo sin necesidad de crear un proceso independiente.

bases de datos
Material relacionado

Cómo mover un repositorio de Git completo

Logotipo de Bitbucket
VER LA SOLUCIÓN

Aprende a usar Git con Bitbucket Cloud

Dependencias de Maven, ramas de función y solicitudes de incorporación de cambios


Todas nuestras compilaciones van a Artifactory. Con SVN, usábamos un repositorio de instantáneas para mantener las últimas 2 compilaciones de instantáneas, un repositorio de entorno de ensayo para las compilaciones de publicación sin aprobar y un repositorio de publicación exclusivamente para las compilaciones con todo listo para ir a producción.[1] Estas compilaciones se numeran como he explicado y se pueden recuperar mediante un patrón de URL predecible basado en el repositorio y la versión.

El flujo de trabajo principal de cada desarrollador era crear una rama de función desde la rama de desarrollo para su trabajo, completarla y enviar una solicitud de incorporación de cambios para que ese trabajo se fusionara de nuevo en la rama de desarrollo. En la mayoría de los proyectos individuales, esto funciona sin problemas. Sin embargo, os voy a hablar del primer problema con el que nos encontramos y que nos hizo reconsiderar seriamente toda la migración:

Como ya he dicho, en nuestros proyectos tenemos varias capas de dependencia. En nuestros productos hay una muy buena razón para ello, tanto histórica como estratégicamente. Nos hemos planteado utilizar otras arquitecturas que acabarían con este problema, pero presentarían otros. Podemos facilitarnos la vida (y lo hicimos, pero eso es para otra entrada), pero por ahora nos resulta estratégico mantener la estructura tal como está.

Imagina que la desarrolladora Angela comienza a trabajar en una función en Jira. Requiere dos ramas: una de nuestro proyecto común y otra del producto X. La versión del proyecto común es 2.1.0-SNAPSHOT. La versión del producto X es 2.4.0-SNAPSHOT. Angela trabaja localmente durante un tiempo y, después, lo envía todo a Bitbucket Data Center. Bamboo recoge los cambios, genera el paquete común y carga common-2.1.0-SNAPSHOT a Artifactory. Luego, genera el producto X con una dependencia de common-2.1.0-SNAPSHOT, cargando también productX-2.4.0-SNAPSHOT. ¡Pruebas unitarias superadas!

El desarrollador Bruce comienza a trabajar en otra función de Jira, para un producto diferente: el producto Y. También requiere dos ramas: una de nuestro proyecto común y otra del producto Y. La versión del proyecto común es, como antes, 2.1.0-SNAPSHOT. La versión del producto Y es 2.4.0-SNAPSHOT. Trabaja localmente durante un tiempo y, después, envía los cambios a Bitbucket Data Center. Bamboo recoge los cambios, genera el paquete común y carga common-2.1.0-SNAPSHOT a Artifactory. Luego, genera el producto X con una dependencia de common-2.1.0-SNAPSHOT, cargando también productX-2.4.0-SNAPSHOT. ¡Pruebas unitarias superadas!

Mientras tanto, Angela encuentra un pequeño error en el código del producto X y escribe una prueba unitaria para validar su corrección. La ejecuta de forma local y la supera. Envía los cambios a Bitbucket, Bamboo recoge el cambio y compila el producto X. La compilación es correcta, pero algunas de sus pruebas unitarias fallan. No son las nuevas que ha escrito, sino las primeras de sus cambios iniciales en la función. ¿La compilación de Bamboo ha encontrado una regresión que no encontró la compilación local? ¿Cómo es posible?

Pues porque su dependencia común, la que Bamboo incorporó al compilar el producto X, ya no era su copia. Bruce sobrescribió common-2.1.0-SNAPSHOT en Artifactory al terminar la compilación de su función. No hubo ningún conflicto con el código fuente: ambos desarrolladores trabajaban de forma aislada en sus propias ramas, pero la fuente de información para la recuperación de artefactos de Maven estaba corrupta.

Menudo fallo.

Durante el mes siguiente después de descubrir el problema, lo probamos todo para sortearlo. A través de nuestro TAM[2], hablamos con personas del equipo de Bamboo que usan git-flow. También hablamos con el desarrollador que mantiene git-flow, una implementación de git-flow en Java. Todos estos contactos fueron muy útiles, pero no dábamos con una solución tolerable, más allá de un proceso que requiriera una lista de pasos manuales que todos los desarrolladores siguieran al trabajar con una función.

Si quieres saber todas las opciones que barajamos, son estas:

1. Modificar el número de versión en el momento de crear las ramas o inmediatamente después.

  • Podemos hacerlo con mvn jgitflow:feature-start para crear la rama.
  • Podemos usar un hook de Bitbucket Data Center o un githook local.
  • Podemos configurarlo manualmente con mvn version:set-version después de crear la rama.
  • Podemos automatizar el cambio con el plugin [maven-external-version].

2. Modificar el número de versión al terminar la rama y fusionarla en desarrollo.

  • Podemos hacerlo con mvn jgitflow:feature-finish para terminar la rama.
  • Usar un controlador git merge para los conflictos de pom.
  • Usar un hook posterior a recepción asíncrono en Bitbucket Data Center

3. Hacerlo todo de forma manual. (Es broma. No nos planteamos esta opción… mucho tiempo).

Posibles flujos de trabajo


Si recuerdas este concepto básico y reflexionas sobre él, entenderás que submodule admite bien algunos flujos de trabajo y no tan bien otros. Hay al menos tres situaciones en las que los submódulos son una buena elección:

  • Cuando un componente o subproyecto cambia demasiado rápido o hay cambios próximos que rompan la API, puedes bloquear el código a una confirmación específica por tu propia seguridad.

  • Cuando tienes un componente que no se actualiza con mucha frecuencia y quieres hacer un seguimiento de él como dependencia del proveedor. Hago esto para mis plugins de Vim, por ejemplo.

  • Cuando delegas una parte del proyecto a un tercero y quieres integrar su trabajo en un momento o publicación concretos. Al igual que antes, es práctico hacerlo así si las actualizaciones no son demasiado frecuentes.

Gracias a finch por estas situaciones tan bien explicadas.

Todas estas opciones tenían algún tipo de efecto secundario negativo. Principalmente: los desarrolladores tenían que hacer pasos manuales cada vez que necesitaran una rama de función, y queríamos que crearan ramas de función todo el tiempo. Además, en la mayoría de los casos, no podíamos usar solicitudes de incorporación de cambios de manera efectiva, lo que era crucial.

Una o dos personas se dedicaron a estas cuestiones durante prácticamente dos meses, hasta que se nos encendió la bombilla y entendimos que estábamos abordando el problema desde una perspectiva equivocada.

Una versión para gobernarlos a todos


Echando la vista atrás, me doy cuenta de que nuestro mayor error fue que centramos nuestra atención en las herramientas de flujo git-flow en lugar de usar las herramientas que ya teníamos para implementar el flujo de trabajo que queríamos. Teníamos:

  • Jira
  • Bamboo Data Center
  • Maven
  • Artifactory Pro

Pues bien: esas eran todas las herramientas que necesitábamos.

Uno de nuestros ingenieros tuvo una idea brillante: dado que el problema no era la gestión de compilaciones en sí, sino que los artefactos se sobrescribían, lo que había que corregir era Artifactory. Tuvo la idea de usar una propiedad de Maven para establecer la URL del repositorio de instantáneas en una URL personalizada que incluyera el ID de incidencia de Jira y, a continuación, escribir sus artefactos en un repositorio creado dinámicamente en Artifactory con una plantilla personalizada. El solucionador de dependencias de Maven encontrará artefactos en el repositorio de instantáneas de desarrollo si no hemos tenido que ramificarlas; por ejemplo, si solo estamos trabajando en un producto y no en un proyecto común también.

Establecemos esa pequeña y práctica variable de propiedad en nuestro archivo de configuración de compilación y escribimos un plugin de Maven para completar la variable durante la primera parte del ciclo de vida de la compilación de Maven. Sobre el papel, parecía una idea fantástica y animó al equipo a trabajar para resolverlo. El problema era que no podíamos hacerlo. La primera etapa del ciclo de vida de Maven es "validar". En el momento en que se ejecutaban los plugins destinados a validar, las URL del repositorio ya estaban resueltas. De esta forma, nuestra variable nunca se completaba y la URL no tenía nombre de rama. A pesar de que habíamos estado usando un diseño en un repositorio independiente de nuestras instantáneas de desarrollo, no estaba aislado para el desarrollo en paralelo.

Otra vez igual, ¡menudo fallo!

Después de relajarse con una cerveza, nuestro ingeniero volvió a la carga y estudió otra forma de añadir funcionalidad a Maven: extensiones.

"Brindemos por el alcohol: causa y, a la vez, solución de todos los problemas de la vida". - Homer Simpson

Las extensiones, igual que los plugins, son muy potentes para mejorar el flujo de trabajo de Maven, pero se ejecutan antes de los objetivos del ciclo de vida y tienen mayor acceso a los datos internos de Maven. Al utilizar el paquete RepositoryUtils, obligamos a Maven a reevaluar sus URL con un analizador personalizado y a restablecerlas con nuestros valores actualizados.[3]

Con la extensión operativa y probada, comenzamos a eliminar tareas previas a la migración una tras otra, pasando de "esto nunca va a pasar" a "esto VA a pasar el lunes… así que tengo que escribir diez páginas de documentación para mañana". Pronto escribiré más sobre cómo contribuyen las herramientas a lograr nuestro nuevo flujo de trabajo de desarrollo y algunas de las lecciones que aprendimos sobre el proceso.


Notas a pie de página

[1]: En este punto, un inconveniente fue que tuve que usar un script que había escrito para llamar a la API de REST de Artifactory y "promocionar" compilaciones desde el entorno de ensayo a la publicación. Es bastante rápido, pero requiere más automatización.

[2]: Gestor de cuentas técnicas. Más información aquí.

[3]: Después del trabajo de desarrollo inicial, descubrimos que teníamos que hacer aún más para que siempre funcionara; por ejemplo, cuando una instantánea es más reciente en Artifactory (de otro ingeniero) que la instantánea local, Maven toma el artefacto remoto porque es MÁS RECIENTE, así que debería estar MEJOR, ¿no?

Matt Shelton
Matt Shelton

Matt Shelton promueve y aplica la metodología DevOps. Supervisa la prestación de los servicios de DevOps (entre otros servicios relacionados) que ofrece Atlassian en el continente americano. Ya rara vez publica algo en su blog, y se centra en crear la próxima generación de promotores de formas más adecuadas de trabajar.


Compartir este artículo

Lecturas recomendadas

Consulta estos recursos para conocer los tipos de equipos de DevOps o para estar al tanto de las novedades sobre DevOps en Atlassian.

Gente que colabora utilizando un muro lleno de herramientas

Blog de Bitbucket

Ilustración de Devops

Ruta de aprendizaje de DevOps

Demostraciones de funciones con expertos de Atlassian del Centro de demostraciones

Cómo funciona Bitbucket Cloud con Atlassian Open DevOps

Suscríbete para recibir el boletín de DevOps

Thank you for signing up