Al igual que las tabulaciones frente a los espacios, las estrategias de ramificación son uno de esos temas sensibles que desencadenan acalorados debates tanto online como offline.
Aunque existen numerosas opiniones firmes sobre el mejor enfoque, como en muchos otros aspectos sobre el desarrollo de software, la respuesta correcta depende del contexto. Teniendo esto en cuenta, veamos qué son las estrategias de ramificación y cómo se vinculan con el CI/CD.
En pocas palabras, una estrategia de ramificación es el acuerdo de su equipo sobre cómo y cuándo crear y fusionar ramas en el control de versiones. La forma en que configure su sistema de control de versiones y el uso de ramas tendrá un impacto en la forma en que configure su proceso de CI/CD, por lo que es importante elegir un modelo que satisfaga sus necesidades.
Los equipos de desarrollo comenzaron a considerar las estrategias de ramificación con el aumento de la popularidad de los sistemas de control de versiones distribuidos, especialmente Git, que facilitó la ramificación.
Con los sistemas distribuidos, hay múltiples copias del repositorio y, por tanto, múltiples fuentes de verdad (aunque es habitual que los equipos designen una copia central o primaria). Usted y los miembros de su equipo trabajan en sus cambios en paralelo en sus copias del repositorio, compartiendo su trabajo al pasar los cambios a otras copias del mismo repositorio.
Git hizo que la creación de ramas y la fusión de confirmaciones de diferentes ramas fuese una tarea muy ligera. En Git, una rama no es más que una confirmación (o conjunto de confirmaciones) etiquetada con un nombre determinado. Las ramas son ideales para contener un conjunto de cambios, como el trabajo en una nueva funcionalidad o en experimento enrevesado.
A continuación, puede compartir sus cambios pasando la rama a otro repositorio o fusionándola con otra rama —como la maestra— para combinarla con el resto del código base. También puede descartar los cambios si decide no realizarlos. Cuando fusiona dos ramas, Git se encarga de alinear las confirmaciones en cada una de ellas y evita gran parte de la complejidad asociada a la fusión en otros sistemas de control de versiones.
Con la integración continua, el objetivo es conseguir que todos los miembros del equipo de desarrollo confirmen sus cambios con frecuencia. Esto significa que puede comprobar regularmente que todo funciona como se espera, en lugar de pasar semanas o meses tratando de integrar diferentes flujos de trabajo después de que la codificación esté completa. A su vez, es posible publicar actualizaciones de software con mayor frecuencia, lo que permite aprovechar las ventajas de la entrega y la implementación continuas.
Decidir dónde deben confirmarse los cambios, cuándo deben ejecutarse las compilaciones y pruebas automatizadas y desde dónde deben publicarse las actualizaciones es el punto en el que el uso de ramas interactúa con CI/CD. El enfoque más sencillo —al menos conceptualmente— es evitar las ramas por completo. En el desarrollo basado en tronco, todo el mundo envía cambios regularmente a la rama maestra en un repositorio central, que se mantiene en un estado listo para el lanzamiento y se implementa con frecuencia a la producción.
Aunque el desarrollo basado en tronco puede funcionar muy eficazmente, en particular si se tiene una configuración madura de CI/CD y se ejecuta una implementación continua en un sistema alojado, también plantea algunos desafíos. Las estrategias de ramificación ofrecen formas alternativas de gestionar los cambios de código, con diferentes ventajas e inconvenientes. Los siguientes son algunas de las más utilizadas por los equipos de desarrollo que ejecutan CI/CD.
Como su nombre indica, las ramas de funcionalidades se crean para mantener las funcionalidades individuales separadas del resto de la base de código. Mantener el trabajo en curso fuera de la rama maestra puede hacer más fácil mantener la rama maestra en un estado listo para el lanzamiento y, si está implementando directamente desde la rama maestra en lugar de una rama de lanzamiento (ver más abajo), evita la posibilidad de que la funcionalidad inacabada se implante a la producción. Puede compartir su trabajo con el resto de su equipo enviando su rama al repositorio central (si ha designado uno) o a cualquier otro repositorio.
El principal inconveniente de las ramas de funcionalidades, y la crítica que a menudo reciben de los defensores del desarrollo basado en tronco, es que al retrasar la integración de los cambios hasta que la funcionalidad esté "completa", se pierden las ventajas de la integración continua. Se corre el riesgo de que se produzcan conflictos de fusión y de que se introduzcan errores más complejos que tardan más en corregirse que si los cambios se hubieran confirmado de forma iterativa.
Puede mitigar estos problemas configurando su servidor CI para que ejecute compilaciones y pruebas automatizadas en las ramas de funcionalidades, así como en la maestra (o en la rama que utilice para preparar los lanzamientos). De este modo, dispondrá de una información inmediata sobre lo que se está compilando, al tiempo que se reduce el riesgo de que surjan problemas realmente fusione los cambios.
Una forma de minimizar los conflictos de fusión es mantener las ramas de funcionalidades durante poco tiempo, hasta un día o dos como máximo. Otra opción es volver a situar la rama en la parte superior de la maestra o fusionar los cambios desde la maestra regularmente. Esto hace que su rama de funcionalidades se actualice con todos los demás cambios confirmados en la maestra. Sin embargo, si tiene un gran número de ramas de funcionalidades concurrentes que retrasan las confirmaciones a la maestra, todavía hay un riesgo de conflictos.
Mientras que las ramas de funcionalidades permiten gestionar el trabajo en curso, las ramas de lanzamiento se utilizan para "afianzar" los cambios antes de su publicación. Las ramas de lanzamiento se adaptan bien a un modelo de entrega continua, en el que las actualizaciones se entregan por intervalos en lugar de tan pronto como están listas, y facilitan el soporte de múltiples versiones en producción.
Con un flujo de trabajo de rama de lanzamiento, una vez que los cambios planificados para una determinada versión están listos, se crea una rama de lanzamiento que contiene los cambios pertinentes (ya sea desde la maestra u otra rama de desarrollo), después de lo cual no se fusionan más funcionalidades. Mientras tanto, el desarrollo de las funcionalidades previstas para otros lanzamientos puede seguir fusionándose en la maestra o en otro lugar.
Las compilaciones de la rama de lanzamiento se someten a una serie de pruebas automatizadas y los errores se corrigen en la rama de lanzamiento, tras lo cual se efectúan otras rondas de pruebas hasta que usted esté listo para su lanzamiento. Esas correcciones de errores también deben aplicarse a la rama maestra para garantizar que se incluyan en futuras versiones del producto.
Mantener las ramas de lanzamiento durante el tiempo necesario para dar soporte a cada versión de su software facilita mucho la implementación de correcciones en las versiones antiguas.
Cuando se requiere una actualización, puede desarrollarse en una rama de hotfix o en la maestra y probarse de forma normal, y luego aplicarse a la rama de lanzamiento (por ejemplo, a través de un cherry-pick).
A continuación, se ejecutará a través del proceso de CI/CD en esa rama para asegurar que no hay problemas en esa versión antes de implementar el cambio. Alternativamente, puede hacer el trabajo directamente en la rama de lanzamiento y aplicarlo de nuevo a la maestra, según corresponda.
Las ramas de hotfix funcionan de forma similar a las ramas de funcionalidades, pero vale la pena mencionarlas para ofrecer una información más completa. El objetivo de una rama de hotfix es llevar una corrección de errores a producción lo antes posible.
Puede crear una rama de hotfix desde la rama maestra o desde una rama de lanzamiento, dependiendo de dónde implemente. Al igual que con las ramas de funcionalidades, puede optar por ejecutar algunos de los elementos de CI/CD en su rama de hotfix antes de fusionarla con la rama de lanzamiento o maestra, donde puede someterse a más pruebas automatizadas antes del lanzamiento.
Hemos visto algunas de las formas más comunes de utilizar las ramas en su flujo de trabajo de CI/CD, pero hay muchas más variaciones.
Lo más importante es encontrar una estrategia que funcione en su contexto específico. Adoptar una mentalidad DevOps de mejora continua y mantenerse abierto a diferentes opciones le permitirá seguir ajustando su enfoque a sus necesidades a medida que su práctica de CI/CD madure.