La integración continua (CI) se ha convertido en un pilar fundamental para cualquier equipo de desarrollo que aspire a acelerar sus ciclos de entrega sin sacrificar calidad ni estabilidad. En Nerve, abordamos la CI no solo como una necesidad operativa, sino como una extensión natural de cómo concebimos el trabajo de desarrollo, poniendo especial énfasis en la rapidez, fiabilidad y autonomía del proceso. Desde mi experiencia inicial en Microsoft allá por 2012, pude apreciar de primera mano los retos que supone manejar pipelines lentos y complejos. Aquella pipeline podía tardar hasta ocho horas en ejecutarse, debido a procesos que iban desde la provisión de máquinas virtuales, la reinstalación completa de sistemas operativos, hasta la instalación y configuración de múltiples servicios. Entonces, trabajar con CI era sinónimo de espera prolongada y frustración, lo que afectaba directamente la motivación del equipo.
Más de una década después, en Nerve, he tenido el privilegio de diseñar un sistema de CI partiendo de cero, con la ventaja de contar con tecnologías más avanzadas y un planteamiento renovado que prioriza la agilidad y la simplicidad. Nerve es un kit de desarrollo universal diseñado para facilitar el consumo de APIs que carecen de un SDK propio o cuyo SDK no satisface completamente las necesidades del usuario. La arquitectura de Nerve se divide en tres componentes fundamentales: la interfaz web del frontend, el backend que funciona como plano de control y el motor de consultas o Nerve Engine, que ejecuta las consultas. Todos escritos en Typescript y gestionados bajo un monorepo homogéneo. El enfoque hacia la CI en Nerve se basa, ante todo, en cumplir objetivos claros como rapidez, estabilidad, reproducibilidad local, accesibilidad para todos los desarrolladores y la generación de artefactos mínimos que no entorpezcan los despliegues.
Para cumplir con estos principios, se ha evitado introducir código especial o soluciones propietarias que añadan complejidad o exclusividad. En su lugar, se apuesta por una filosofía de monorepo simple y centralizada, donde las herramientas de construcción, scripts y código del producto coexisten de forma coherente y reproducible. Uno de los grandes retos en la gestión de Nerve ha sido equilibrar la velocidad en la compilación y testeo del código. Con alrededor de trescientas mil líneas de código en Typescript, sin incluir dependencias, se maneja una base de código de tamaño medio, que requiere optimizaciones para mantener tiempos de construcción bajos. La solución incluye aprovechar las capacidades nativas de Typescript, como la compilación incremental mediante el flag -b, que permite reconstruir únicamente los paquetes que han cambiado y sus dependientes.
Sin embargo, para que dicha compilación incremental funcione eficazmente en el entorno CI, se hace necesario contar con builds previas que sirvan de base. Para ello, desarrollé una utilidad llamada "hydrator" que facilita la carga y almacenamiento de builds entre diferentes ejecuciones. Su funcionalidad de mover archivos comprimidos de builds previamente exitosos hacia el monorepo local, y viceversa, optimiza el proceso y reduce la necesidad de reconstrucciones completas, acelerando significativamente la CI. Un concepto fundamental es que el proceso de construcción debe poder reproducirse localmente sin fricciones. Esto es vital para facilitar la depuración y para que los desarrolladores puedan experimentar o diagnosticar problemas sin depender exclusivamente de entornos remotos o infraestructuras externas.
Además, se preserva el centro de gravedad del desarrollo, es decir, que los desarrolladores no tengan que modificar su forma natural de escribir código para ajustarse a requisitos de la pipeline. La gestión eficiente de dependencias es otro pilar dentro del proceso. Usamos Yarn con workspaces para manejar dependencias de múltiples paquetes en la monorepo, lo que simplifica la instalación y mantiene coherencia. Reconociendo que las operaciones de red como las instalaciones de dependencias son altamente variables e inestables, optamos por almacenar y versionar caches completos del directorio de Yarn en DigitalOcean Spaces, optimizando para velocidad y fiabilidad en CI. Para garantizar la seguridad y confianza en la cadena de suministro de código, especialmente en la recuperación y utilización de artefactos y caches, se implementa un sistema de firma y validación de builds.
Esto protege contra posibles manipulaciones maliciosas que puedan comprometer la integridad o seguridad del software desplegado. En lo que respecta a testing, el diseño se fundamenta en separar estrictamente la lógica pura e independiente del estado de los adaptadores dependientes del entorno. Esto se traduce en pruebas unitarias rápidas, confiables y sencillas, orientadas a funciones puras, y pruebas de integración intra-proceso que simulan flujos complejos combinando componentes, pero ejecutándose en un único proceso para minimizar la dependencia de infraestructura y mantener la velocidad. La estrategia de testing evita la complejidad derivada de la infraestructura tradicional como bases de datos de pruebas o testing de interfaces complejas con herramientas como Selenium, apostando en cambio por la simplicidad y gran cobertura funcional, que ofrece ventajas en tiempos y estabilidad. En cuanto al empaquetado, Nerve produce artefactos específicos para cada componente: un bundle para el frontend y contenedores para backend y engine.
La construcción de estos artefactos se realiza procurando minimizar el tamaño y eliminar peso muerto, empleando herramientas como esbuild para el frontend, que ofrece bundling, tree shaking y minificación rápidas y eficientes. Para las imágenes de contenedores, se aplica una estrategia która permite construir los contenedores aislados de la monorepo Git, publicando paquetes de primera mano a un registro local temporal (verdaccio) y utilizando la caché de paquetes para optimizar instalacion y tamaño. Este método reduce la necesidad de copiar todo el código fuente en la imagen y favorece builds reproducibles y ligeros, lo que además repercute en despliegues y actualizaciones más ágiles. Los scripts y flujos de CI están completamente orquestados en Github Actions, con jobs dedicados a construcción, testing, empaquetado y limpieza o rollback de builds fallidos, todo ello monitorizado y ejecutado bajo contenedores especializados que garantizan entornos replicables y consistentes. Como perspectiva futura, está planificada la actualización a una versión más moderna de Typescript, específicamente tsc 7, que promete mejoras sustanciales en el rendimiento de compilación, lo que podría simplificar o eliminar algunas de las soluciones de cache actuales y acelerar aún más los tiempos de CI.
También se proyecta mejorar el manejo del cache de Yarn para evitar acumulaciones innecesarias que podrían inflar los recursos almacenados y afectar la velocidad o estabilidad del proceso. En resumen, la experiencia de Nerve con integración continua demuestra que un diseño cuidadoso basado en principios claros y tecnología actual puede ofrecer un pipeline rápido y confiable, favoreciendo la productividad de los desarrolladores y la calidad del producto. La filosofía de hacer que todo el proceso sea localmente reproducible, accesible a ingenieros no especializados en infraestructura y auto contenido, hace que el desarrollo no solo sea más ágil, sino también más agradable y motivador para quienes trabajan en él. Al adoptar estos principios, otras organizaciones pueden reducir la frustración derivada de pipelines lentos o complejos, optimizando tiempo y recursos para centrarse en lo más importante: construir software excepcional.