Explota todo el potencial del versionamiento de tus componentes de software

Gabriel Martínez
Bancolombia Tech
Published in
10 min readFeb 17, 2023

¿Qué es versionamiento de Software?

Es la forma de identificar un componente de software de la manera más clara, conociendo su evolución durante la adición de nuevas funcionalidades (features), sus actualizaciones y/o correcciones a los problemas que puedan detectarse.

¿Versionas tus componentes de Software?

Si respondiste sí, entonces, lo que deberías preguntarte después es: ¿lo estoy haciendo bien? O, por el otro lado, ¿tu respuesta es que no o que no sabes si versionas tus componentes de software? Cualquiera sea el caso te invito a llegar hasta el final de este artículo y que me dejes en los comentarios si lo que leíste, de alguna manera cambia tu perspectiva sobre este tema.

Todo en una organización gira en torno a la comunicación, es decir, es la fuerza motriz de los esfuerzos combinados que hacemos en nuestro trabajo. Además, tener una comunicación efectiva con los miembros de nuestro equipo, con otros equipos y con nuestros clientes, determinará la calidad del producto o servicio que entregamos.

Ahora bien, ¿qué tiene que ver el versionamiento de mis componentes de software con la forma en que realizamos nuestra comunicación?

Pues bien, en realidad el tema va más allá de un simple número y se relaciona con todas las cosas que giran alrededor de cómo la estrategia combina, coordina y da visibilidad a todos los que tienen que ver directa o indirectamente con los componentes que construiste.

Versionamiento Estratégico

En especial, si estamos en un modelo de trabajo ágil, es recomendado que, desde las etapas tempranas de definición o diseño de los componentes de software que construiremos, sea visible para todos los interesados la forma en que será liberada cada entrega de valor y su evolución. Es decir, deberíamos empezar por crear un roadmap de producto.

Las organizaciones suelen distribuir las entregas de valor en periodos (trimestres, cuatrimestres, etc.), generalmente llamados Quarters por su definición en inglés o simplemente Q´s.

Como usualmente son diferentes equipos trabajando dentro de la organización, varios de los desarrollos construidos, impactarán en uno o más componentes para incrementar su funcionalidad. Por lo tanto, es importante que estos equipos estén alineados en cuanto a responder una pregunta: ¿En qué Q deberían ser liberadas ciertas funcionalidades o mejoras? De esta manera todos los involucrados pueden sincronizar sus esfuerzos estratégicamente.

Ejemplo del mapa de contextos de una solución de E-commerce.

Es aquí donde los responsables de esos componentes pueden empezar a definir un roadmap de evolución para sus productos según lo planeado. Incluso, pueden hacerlo de manera tentativa, relacionar un # de release mayor (si se usa versionamiento semántico), asociado a esas funcionalidades que se están comprometiendo.

Ejemplo roadmap componentes

Un ejemplo real de cómo puede verse esta planeación puede ser el Roadmap público de Github:

Roadmap público de Github
Roadmap público de Github

Versionamiento Táctico

En la etapa de definición estratégica, debieron ser planteadas a alto nivel las entregas de valor que se realizarán para el o los próximos periodos. Ahora es el turno de realizar el diseño táctico de dichas funcionalidades para determinar cómo se cumplirá con esa entrega de valor, en el periodo comprometido.

A propósito, si deseas profundizar en las fases de modelado estratégico y táctico del Domain Driven Design, te invito a leer este artículo: Domain driven design en Bancolombia — De lo estratégico a lo táctico | by Daniel Estiven Rico Posada | Bancolombia Tech | Medium

Como consecuencia de ese diseño, los responsables de los componentes tendrán una idea más clara del trabajo a realizar. Este puede ser divido en épicas, que luego son repartidas en elementos de backlog y, posteriormente, estimadas y distribuidas a lo largo de los sprints existentes del Q. Esto podría convertirse en un plan de hitos.

La importancia de hacer esto es contar con algún plan que determine cuándo una característica (feature) será entregada a un cliente o consumidor.

Ejemplo de Plan de Hitos

En la imagen anterior, podemos ver como podría ser la planeación para un periodo del Componente A, para construcción de una capacidad o característica llamada “Pagos con QR”.

  • La Funcionalidad X, es una mejora a una funcionalidad ya existente y el equipo se comprometió a liberarla en el sprint #2. Es un pre-requisito para la funcionalidad de Pagos con QR. Al ser modificaciones de una funcionalidad existente, el equipo estimó que la versión menor, debería incrementar de 1.2.1 a 1.3.0
  • La Funcionalidad Y, es la funcionalidad core para implementar el pago con QR, el equipo estima que puede estar lista para el sprint 4, y define que la versión mayor saltaría de 1.3.0 a 2.0.0.
  • Por último, la Funcionalidad Z, son mejoras menos críticas relacionadas con la funcionalidad de pago con QR, y el equipo estima que estas mejoras se podrían liberar en el último sprint del periodo. A su vez, estima que la versión menor es la que se debería incrementar. Por lo tanto la versión saltaría de 2.0.0 a 2.1.0.

Este tipo de planeación da visibilidad a cualquier consumidor del Componente de Pagos. En ese sentido sabrán cuándo y qué versión incluye la mejora o funcionalidad que están esperando que sea liberada.

Ahora bien, llegados a este punto, vale la pena volver a preguntarles ¿Cómo están versionando sus componentes? ¿Les suena familiar liberar sus componentes con un ID de versión así ms-pagos-trunk.20230105.build34?

  • ¿Será fácil o más natural comunicar ese ID a un consumidor de mi componente?
  • ¿Será fácil para un consumidor saber si ese ID de versión precede o antecede otro ID de versión?

Trazabilidad

La planeación de periodos y la formalización de un plan de hitos marcan la evolución del producto hacia el futuro. Sin embargo, a medida que las funcionalidades planeadas son liberadas, es importante tener un registro de esta evolución de manera histórica.

Changelog
Mantener una relación entre las funcionalidades liberadas y las versiones en las que fueron entregadas, habilita una herramienta importante de cara a los consumidores y contribuidores de dichos componentes, como lo es el registro cronológico de los cambios o aportes mas notables del componente. Este registro de cambios es esencialmente para personas, no para máquinas. De manera que todos puedan conocer qué se ha venido liberando en cada entrega.

Tags
A parte de la trazabilidad histórica, crear tags con los ID de las versiones exactas, en el sistema de control de versiones (github, gitlab, etc.), permite que los equipos de desarrollo se remitan a esas versiones especificas para revisar y atender bugs o vulnerabilidades detectadas. El manejo o no de tags depende también del flujo de trabajo que manejen los equipos (Trunk Based Development, Gitflow).

Ecosistema

Relaciones entre componentes y Heatmaps
En el ejemplo de solución de e-commerce que hemos venido usando es fácil identificar las relaciones entre componentes y sus versiones. No obstante, cuando estamos ante una solución empresarial de un tamaño considerable, es imposible tener todas estas relaciones y dependencias en la cabeza. Por ello, es importante poder tener un inventario de los componentes, las APIs y las relaciones entre cada uno de estos.

La idea detrás de contar con esta información, es poder tener la habilidad de tomar decisiones con base en los impactos en los microservicios relacionados cuando estos se van a alterar o se les agregarán nuevas funcionalidades.

Componentes afectados si se modifica de una manera no retro compatible el componente de Inventario

Existen herramientas en el mercado para mantener este tipo de relaciones. En el equipo de Bancolombia estamos explorando Backstage.io, que nos permite tener la capacidad de administrar el catálogo de aplicaciones, servicios y muchas otras capacidades de cara al desarrollador. En el futuro cercano probablemente realizaremos un artículo para que conozcan más al respecto 🤓.

Más que código
Es importante que los cambios relevantes derivados de la implementación de una capacidad o característica no estén siendo versionados solo en el código aplicativo. Hay componentes que deberían estar versionados de la misma manera para mantener la trazabilidad. Entre estos componentes podemos considerar:

  • Pipelines de CI-CD
  • Manifiestos de despliegue
  • Infraestructure-As-Code (IaC)
  • Imágenes de contenedores

Habilitar modelos de despliegue

Continuous integration
A nivel de la CI los builds deberían ser siempre reproducibles. Es decir, en cualquier momento el equipo debería poder tomar una versión antigua del código y volver a compilarlo y generar el mismo artefacto que se generó en su momento.

Pero, ¿cómo logras esto sí?:

  • Las versiones de tus componentes son algo así como “ms-pagos-trunk.20230105.build34” y cada vez que ejecutas un pipeline sobre el mismo código, sin ningún cambio, las versiones producidas son cada vez diferentes (incrementales, dados el timestamp y el número de build).
  • No tienes versionados pipelines y otros artefactos diferentes al código, ante los cambios, adición de políticas, y mejoras que les vas adicionando.

Continuous delivery
Las organizaciones que quieren habilitar modelos de Continuous Delivery deben ser capaces de integrar software de manera rápida, crear ejecutables, correr pruebas de manera automática para descubrir problemas y tener la capacidad de ir a producción en cualquier momento.

Esto implica una relación colaborativa muy fuerte entre todos los equipos que participan en el despliegue, no solo el equipo que desarrolla. Por lo tanto, saber siempre qué se está construyendo o desplegando en una nueva versión, es fundamental.

¿Es posible habilitar modelos de despliegue como Canary o Blue/Green en entornos donde no se versiona correctamente el software y artefactos generados? Sí, pero sin un mecanismo claro de versionamiento de TODOS los artefactos involucrados podría dificultarse todo el proceso de scripting para liberar parcialmente o gradualmente una solución. Quizás, aplicando un rollback a una versión anterior de manera automática, ya que deberíamos tener más validaciones manuales de las necesarias, que sumando tiempos, afectaría la definición de liberar rápidamente.

Soporte

Ese componente del que tanto te enorgulleces y que usan el 80% de los microservicios implementados por los diferentes equipos de la organización, es ese mismo que te hace reflexionar sobre las decisiones que has tomado en tu vida…. “…¿y si de verdad hubiera decidido ser un Gamer Profesional?”

Ese 80% de microservicios, digamos que no siempre están usando la última versión que liberas mes a mes de tu componente y cada uno de esos equipos llegan a ti con reportes de bugs o peticiones para adicionar funcionalidades.

Esta cantidad de trabajo puede agobiarte a ti o al equipo que soporta y evoluciona ese componente.

  • Si no tienes un changelog, ¿cómo comunicas a todos esos equipos que ciertos bugs ya fueron resueltos en una versión?
  • Si no tienes un Roadmap o un plan de hitos, ¿cómo comunicas a esos equipos cual será la evolución proyectada de tu componente, de esas funcionalidades que saldrán paulatinamente según la cadencia de trabajo y la capacidad de tu equipo?
  • Si tienes 40 versiones disponibles de tu componentes nombradas así: “ms-pagos-<nombre de rama>.<timestamp>.build<número de build>”,¿cómo le defines a tus consumidores una guía de migración de una versión de un componente a otra?
  • Adicionalmente, ¿cómo enfocas a tu equipo para definir que cierta línea de versión mayor (hablando de versionamiento semántico) va a ser considerada cómo LTS (Long Term Support) y es en la que te vas a concentrar en liberar bug fixes o mejoras, para optimizar la capacidad que tienes?

Versionamiento Semántico

A lo largo del artículo hicimos referencia de manera implícita a un mecanismo de versionamiento semántico (SemVer). Para explicarlo de una manera sencilla, básicamente tenemos 3 números concatenados:

  • Versión Mayor: Este número debe incrementar cuando realizas un cambio incompatible en el API. En este punto API se entenderá como el conjunto de interfaces y objetos que hacen parte de tu componente.
  • Version Menor: La versión MENOR debe incrementar cuando añades funcionalidad compatible con versiones anteriores, es decir, retro-compatibles.
  • Version Patch: Cuando reparas errores compatibles con versiones anteriores.

Así puedes definir fácilmente qué parte incrementar dadas ciertas mejoras, arreglos o adiciones de funcionalidad que hagas en tus componentes.

Podrías incluso adoptar el uso de Conventional Commits, la cual es especificación para describir y mantener una historia de commits que sean faciles de entender para los humanos y hasta para las máquinas.

Un ejemplo de una práctica deficiente y que no aporta mucho a la historia evolutiva de un componente puede ser la siguiente:

Pero en cambio, si usáramos conventional commits, un commit podría verse así:

feat(notifications)!: send confirmation email to the customer

BREAKING CHANGE: send an email to customer after order

La plantilla para estos mensajes es la siguiente:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

La idea es usar unos elementos estructurales para comunicar nuestra intención sobre los cambios realizados:

  1. fix: un commit del tipo fix resuelve un bug en nuestro código (esto equivale a la versiónPARCHE en Versionamiento Semántico).
  2. feat: un commit del tipo feat introduce una nueva característica a nuestra base de código (esto equivale a la versiónMENOR).
  3. BREAKING CHANGE: un commit que tiene un footer BREAKING CHANGE:, o que agrega un ! después del type/scope, está incorporando un cambio que rompe el API (equivale a la version with MAYOR ).
  4. Tipos de commit diferentes al fix: y feat: son permitidos, por ejemplo @commitlint/config-conventional (basado en Angular convention) como por ejemplobuild:, chore:, ci:, docs:, style:, refactor:, perf:, test:, y otros.
  5. footers diferentes al BREAKING CHANGE: <descripción> son permitidos y siguen una convención similar al git trailer format.

Ahora al usar conventional commits puedes explotar el potencial de herramientas que analizan los commits y deciden:

Entre otras herramientas que puedes usar en tus pipelines de CI/CD para optimizar el proceso de versionado de tus componentes.

Llegamos al final, y es momento de rescatar la pregunta que hicimos al inicio. ¿Cambió tu perspectiva sobre este tema?, Además me atrevería a añadir:

  • ¿Te sientes retado a cambiar/mejorar los procesos actuales para incorporar alguna de las prácticas que recomendamos?
  • ¿Cuáles crees que deben ser los siguientes pasos para iniciar con estas prácticas?
  • ¿Has incorporado alguna práctica adicional, que no hayamos mencionado en este artículo?

Queremos leer sus respuestas en la sección de comentarios.

Happy coding and versioning!

--

--