extreme programming10 min de lectura

¿Qué es Trunk-Based Development?

Por Sergio Perea #version control
¿Qué es Trunk-Based Development?

Trunk-Based Development (TBD) es una estrategia de control de versiones que promueve la colaboración continua y la integración frecuente de cambios en una única rama principal del código, conocida como "trunk" o "main". Este enfoque busca minimizar los conflictos de integración y acelerar el ciclo de entrega de software. A continuación, se presenta un resumen detallado de los principios, beneficios y consideraciones clave de TBD.


¿Qué es Trunk-Based Development?

Trunk-Based Development es una práctica de gestión de control de versiones en la que los desarrolladores integran pequeños y frecuentes cambios en una rama principal compartida, denominada "trunk" o "main". Este modelo evita la creación de ramas de desarrollo de larga duración, promoviendo en su lugar la integración continua y la entrega rápida de software.

A diferencia de otros enfoques como Git Flow o Feature Branching (en la imagen), que fomentan el uso de múltiples ramas (por ejemplo, develop, release, hotfix, feature/*), Trunk-Based Development busca simplificar el flujo de trabajo y reducir los riesgos de integración tardía.

Mientras Git Flow es útil en entornos con ciclos de lanzamiento largos y estructuras más formales, Trunk-Based Development está más alineado con metodologías ágiles y DevOps, donde la prioridad es la automatización, la productividad y la colaboración constante entre equipos.

Al eliminar la complejidad innecesaria del manejo de ramas prolongadas, esta estrategia facilita el testing automatizado, el deployment continuo y una mayor calidad en el software entregado.

TBD Workflow


Principios fundamentales de TBD

Los iremos desarrollando a lo largo de este artículo. De momento te los enumero:

  1. Integración continua: Los desarrolladores integran sus cambios en la rama principal varias veces al día, lo que facilita la detección temprana de errores y reduce los conflictos de integración.

  2. Ramas de corta duración: Si se utilizan ramas, estas son de vida corta, generalmente de horas o pocos días, y se integran rápidamente al trunk.

  3. Revisión de código continua: Las revisiones de código se realizan de manera continua y rápida, lo que mejora la calidad del código y facilita la colaboración entre desarrolladores.

  4. Automatización de pruebas: Se implementan pruebas automatizadas que se ejecutan con cada integración para garantizar la estabilidad y calidad del código.

  5. Uso de feature flags: Se emplean banderas de características para activar o desactivar funcionalidades en producción sin necesidad de desplegar nuevo código, permitiendo una mayor flexibilidad y control.


Beneficios de Trunk-Based Development

  • Reducción de conflictos de integración: Al integrar cambios frecuentemente, se minimizan los conflictos que suelen surgir al fusionar ramas de larga duración.

  • Entrega continua: El código en la rama principal está siempre en un estado desplegable, lo que permite realizar entregas frecuentes y rápidas.

  • Mejora en la calidad del código: Las revisiones continuas y las pruebas automatizadas contribuyen a mantener un código de alta calidad.

  • Mayor colaboración: Al trabajar en una única rama compartida, se fomenta la colaboración y comunicación constante entre los miembros del equipo.


Comparación con GitFlow

Mientras que GitFlow utiliza múltiples ramas de larga duración para gestionar el desarrollo, las correcciones y las versiones, TBD se centra en una única rama principal con integraciones frecuentes. GitFlow puede ser más adecuado para proyectos con ciclos de lanzamiento definidos y necesidades de mantenimiento específicas, mientras que TBD es ideal para equipos que buscan una entrega continua y rápida.

Paso 1: Establecer una Rama Principal Compartida

Introducción

El primer paso fundamental para adoptar Trunk-Based Development (TBD) es establecer una rama principal compartida como centro neurálgico del desarrollo. Esta rama es comúnmente denominada main o trunk, y todos los desarrolladores integran directamente (o a través de ramas muy breves) su trabajo en ella.

Esta práctica representa un cambio de paradigma frente a otros flujos de trabajo más tradicionales como Git Flow, donde las ramas de desarrollo (develop, feature/*, release/*, hotfix/*) son fundamentales. En TBD, simplificamos la topología del repositorio para maximizar la velocidad de integración y minimizar la divergencia.


¿Por qué una sola rama?

Mantener una única rama principal compartida tiene los siguientes beneficios:

  • Evita la deriva de código entre ramas largas.
  • Promueve la integración continua, ya que todos los cambios convergen rápidamente.
  • Minimiza el coste de la fusión, al hacer merges pequeños y frecuentes.
  • Aumenta la visibilidad del estado actual del producto, porque lo que hay en main es lo que hay.

Pasos para establecerla correctamente

1. Nombrar la rama principal

La convención moderna recomienda usar main como nombre de la rama principal en lugar de master, por razones de claridad y neutralidad. En proyectos más antiguos, puedes renombrar así:

git branch -m master main git push -u origin main git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main

2. Bloquear el uso de ramas largas

Configura el entorno de desarrollo y los repositorios (por ejemplo, en GitHub, GitLab o Bitbucket) para:

  • Restringir la creación de ramas de larga duración.
  • Desalentar el uso de ramas develop, release, etc.
  • Establecer reglas claras para pequeñas ramas temporales (máx. 1–2 días).

3. Activar protección y revisión en main

Aunque TBD impulsa la integración frecuente, eso no significa que se omita la calidad. Puedes:

  • Activar branch protection rules que exijan que los commits a main pasen por PR (pull request).
  • Requerir revisiones ligeras o rotativas.
  • Automatizar pruebas para impedir integraciones defectuosas.

En GitHub, por ejemplo:

Settings > Branches > Branch Protection Rules > Require status checks to pass before merging

4. Automatizar la integración y las pruebas

Cada cambio en main debe:

  • Ser testeado automáticamente mediante CI (por ejemplo, GitHub Actions, GitLab CI/CD, CircleCI).
  • Lanzar procesos de despliegue automáticos si corresponde (CD).

Este pipeline debe ser rápido (<10 minutos idealmente), confiable y visible para todos.

CI Pipeline

5. Educar al equipo

Cambiar a TBD requiere compromiso del equipo. Algunas acciones útiles:

  • Realizar formaciones o talleres sobre flujo de trabajo con una rama compartida.
  • Establecer normas: "nunca rompas main", "integra al menos una vez al día", "usa feature toggles", etc.
  • Incentivar la colaboración en cambios grandes para evitar bloqueos.

Buenas prácticas adicionales

  • Commits pequeños y atómicos: Evita grandes commits que dificulten las revisiones y el troubleshooting.
  • Mensajes de commit claros: Usa convenciones como Conventional Commits (feat:, fix:, refactor:…).
  • Feature toggles: Activa/desactiva funcionalidades en producción sin necesidad de ramas largas.

Veamos un ejemplo de como activar una nueva funcionalidad con feature toggle en Python

Supongamos que estás desarrollando una API y quieres introducir una nueva funcionalidad de búsqueda avanzada, pero sin romper la versión actual en producción. En lugar de crear una rama larga, usas un toggle para activarla o no:

# config.py FEATURE_FLAGS = { "advanced_search": False # Cambia a True para activarla }
# main.py from config import FEATURE_FLAGS def basic_search(): return "Ejecutando búsqueda básica..." def advanced_search(): return "Ejecutando búsqueda avanzada con filtros..." def search(): if FEATURE_FLAGS["advanced_search"]: return advanced_search() return basic_search() if __name__ == "__main__": print(search())

Ahora hacemos commit, dejando un mensaje claro:

git add main.py config.py git commit -m "feat: añade feature toggle para búsqueda avanzada"
  • feat: indica que estás añadiendo una nueva funcionalidad al código (útil para changelogs automatizados).
  • El resto del mensaje describe claramente qué se está incorporando: el feature toggle para activar o desactivar la búsqueda avanzada.

Otros ejemplos comunes de mensaje en el commit incluirían:

  • fix: corrige error al desactivar la búsqueda avanzada
  • refactor: reorganiza lógica de búsqueda sin cambiar funcionalidad
  • test: añade pruebas unitarias para toggle de búsqueda avanzada

Posibles dificultades

  • Rupturas accidentales: Si alguien introduce código defectuoso en main, todos se ven afectados. Por eso CI y revisión son imprescindibles.
  • Resistencia al cambio: Equipos acostumbrados a Git Flow pueden tener miedo de "romper cosas". Aquí es clave la formación.
  • Cambios grandes: Para grandes refactors o features complejas, usar feature branches de muy corta duración o técnicas como branch by abstraction.

Ejemplo realista

Supón que trabajas con un equipo de 4 desarrolladores. Todos comparten la rama main y se organizan así:

  • Cada uno crea una rama feat/nueva-funcionalidad y hace push el mismo día.
  • Cada PR se revisa rápidamente (máximo 30 minutos desde que se abre).
  • Al hacer merge, los tests corren automáticamente.
  • Si todo va bien, se despliega en staging automáticamente.

En este contexto, no hay ramas develop, ni release, ni semanas de trabajo sin integración.


Paso 2: Fomentar Integraciones Frecuentes

Uno de los principios esenciales del Trunk-Based Development (TBD) es que los desarrolladores deben integrar sus cambios en la rama principal varias veces al día. Esta práctica, conocida como frequent commits o continuous integration, es vital para mantener un código limpio, coherente y en constante evolución.

Integrar con frecuencia no es solo un hábito técnico, sino una filosofía que prioriza la comunicación continua entre personas, la visibilidad colectiva del código y la responsabilidad compartida del producto.


¿Qué significa integrar con frecuencia?

En el contexto de TBD, "integrar frecuentemente" implica:

  • Que cada desarrollador sincroniza su trabajo con el trunk al menos una o dos veces por jornada de trabajo.
  • Que las integraciones son pequeñas y manejables, no grandes paquetes de cambios acumulados.
  • Que el trunk se mantiene siempre en un estado estable, desplegable y verificable mediante pruebas automáticas.

Beneficios de integrar frecuentemente

1. Reducción de conflictos

Integrar frecuentemente reduce drásticamente los conflictos de fusión (merge conflicts), ya que:

  • El código divergente se detecta rápidamente.
  • Las diferencias entre desarrolladores se resuelven en horas, no en semanas.
  • Se evita la "sorpresa" de cambios incompatibles acumulados.

2. Visibilidad y colaboración

Cuando todos integran con regularidad:

  • Todo el equipo ve lo que los demás están haciendo en tiempo real.
  • Se fomentan conversaciones técnicas tempranas y constructivas.
  • El conocimiento del sistema se distribuye de forma natural.

3. Mejora continua

Las integraciones frecuentes habilitan:

  • La mejora constante del producto.
  • El feedback inmediato tras cada cambio (gracias a CI/CD).
  • La corrección rápida de errores y la iteración ágil.

Requisitos para que funcione

Para que la integración frecuente sea efectiva, deben darse ciertas condiciones técnicas y culturales.

1. Tests automáticos rápidos y fiables

Cada push al trunk debe ejecutar una batería de pruebas. Cuanto más rápido y fiable sea este feedback, más seguro será integrar a menudo.

  • Unit tests en segundos.
  • Integration tests en minutos.
  • Fail fast: que fallen rápido y con mensajes útiles.

2. Integración continua (CI) bien configurada

Herramientas como GitHub Actions, GitLab CI, Jenkins o CircleCI deben estar integradas al repositorio para:

  • Ejecutar automáticamente los tests tras cada push o PR.
  • Notificar a los desarrolladores del resultado.
  • Bloquear merges si los tests fallan.

3. Desacoplamiento y modularidad

Es difícil integrar cambios frecuentemente si el código está muy acoplado. Algunos principios útiles:

  • Single Responsibility Principle.
  • Micro-commits: cada cambio realiza una tarea clara.
  • Uso de interfaces o abstracciones para facilitar refactors incrementales.

4. Pequeños pasos (baby steps)

Romper funcionalidades grandes en pequeños pasos ayuda a:

  • Integrar más rápido.
  • Probar partes aisladas.
  • Usar feature toggles para funcionalidades incompletas.

5. Uso de Feature Flags

Las banderas de características permiten integrar código en producción sin activarlo. Esto:

  • Elimina la necesidad de mantener ramas largas.
  • Permite pruebas A/B y despliegues progresivos.
  • Facilita revertir sin hacer rollback de código.

Técnicas para lograrlo

Commit temprano, commit a menudo

Realiza pequeños commits funcionales y súbelos tan pronto como sea posible.

Push en medio del trabajo

No esperes a terminar todo para hacer push. Es preferible subir parte del trabajo, protegido por un feature flag, a quedarte con días de trabajo fuera del trunk.

Pull frecuente del trunk

Antes de comenzar el día o cada pocas horas:

git pull origin main

Mantener tu entorno sincronizado evita sorpresas desagradables al hacer merge.

Short-lived branches

Si usas ramas de funcionalidad, que duren solo unas horas o un día. Ejemplo:

git checkout -b feat/nueva-validacion # Haces el cambio git push origin feat/nueva-validacion # Abres PR -> CI -> revisión rápida -> merge

Qué evitar

  • ❌ Ramas que duran varios días.
  • ❌ Esperar a "tenerlo todo listo" para integrar.
  • ❌ Introducir cambios grandes sin tests.
  • ❌ Integrar sin revisar el estado del trunk (pull).
  • ❌ Depender solo de integración manual (sin CI/CD).

Cultura de equipo: un factor clave

Integrar frecuentemente es una práctica que debe estar respaldada por la cultura del equipo. Esto incluye:

  • Confianza entre desarrolladores.
  • Valentía para subir código incompleto (pero protegido).
  • Disciplina para escribir tests desde el principio.
  • Revisión rápida y constante de PRs.
  • Tolerancia al cambio constante, entendiendo que el código está vivo.

Ejemplo práctico

En un equipo que sigue TBD correctamente:

  • Pedro comienza su día, hace git pull origin main.
  • Crea una rama feat/nueva-alerta, y en 2 horas termina la primera parte.
  • Abre una PR con tests y la etiqueta #WIP (Work In Progress).
  • Su compañero Jaime la revisa al momento y sugiere un cambio.
  • Pedro lo aplica, se pasa CI, y Jaime mergea a main.
  • Al final del día, Pedro ha integrado 3 veces a main.