Skip to content
José Bocanegra edited this page Mar 7, 2023 · 6 revisions

Banner Curso

Lo básico de la Integración Continua

Los artefactos de software

Usualmente, en un proyecto de desarrollo de software se producen muchos archivos de código, de configuraciones, de datos, scripts, etc. Todos estos artefactos son creados, modificados, extendidos, adaptados por distintas personas a través del tiempo. ¿Cuánto tiempo? Dependerá, primero de la duración del proyecto de desarrollo inicial (meses, quizás unos años) y luego de la vida útil del producto, que podría ser un año, 5, 10 o más años. Administrar correctamente estos artefactos es muy importante para evitar un caos que puede llevar a pérdida de información, pérdida de tiempo por rehacer cosas que no se encuentran o que no se sabe cuál es el estado, poca eficiencia en el desarrollo y sobre todo un impacto negativo importante en la calidad de lo que se hace.

Una vez que se haya decidido la arquitectura del proyecto, la vista de desarrollo debe ayudar a organizar todos estos artefactos y debe haber un claro mapeo entre los conceptos de alto nivel de la arquitectura y los proyectos (carpetas, archivos, esquemas de nombramiento, entre otros). Por ejemplo, como hemos visto en el proyecto Book, este tiene cuatro capas funcionales: WEB, API-REST, Lógica y Persistencia. Estas capas están distribuidas en dos repositorios git. Uno contiene la aplicación WEB y el otro contiene tres proyectos: el proyecto del API-REST, el proyecto del back (que incluye la Lógica y la Persistencia) y el proyecto que contiene los procedimientos para construir y desplegar las capas anteriores en un servidor de aplicaciones.

Dentro de cada uno de estos proyectos, también existe un esquema de nombramiento de paquetes, archivos de código y archivos de configuración que facilita la distribución del trabajo y la integración. Cada uno en el equipo debe tener claro cómo se deben llamar los artefactos que produce y en dónde deben ir dentro de la estructura de los proyectos y de los repositorios.

En nuestro esquema, la guía principal del desarrollo, de donde parte todo, es el diagrama conceptual inicial, que representa la identificación de los recursos de nuestra aplicación. A medida que se avanza en el desarrollo del proyecto, cada elemento del diagrama se va transformando en artefactos concretos en cada una de las capas. En la siguiente tabla mostramos un ejemplo del proyecto Book. Definimos para el concepto Editorial del modelo conceptual, los elementos en los que se transformó y el archivo (artefacto de software) correspondiente:

Clase Archivo
Proyecto backstepbystep-api
EditorialCollectionTest backstepbystep-api/collections/EditorialCollectionTest.json
EditorialDTO co.edu.uniandes.csw.books.dtos.EditoriaDTO.java
EditorialDetailDTO co.edu.uniandes.csw.books.dtos.EditoriaDetailDTO.java
EditorialResource co.edu.uniandes.csw.books.resource.EditoriaResource.java
EditorialBooksResource co.edu.uniandes.csw.books.resource.EditoriaResource.java
Proyecto backstepbystep-back
EditorialEntity co.edu.uniandes.csw.books.entities.EditorialEntity.java
EditorialPersistence co.edu.uniandes.csw.books.persistence.EditorialPersistence.java
EditorialPersistenceTest co.edu.uniandes.csw.books.persistence.test.EditorialPersistenceTest.java
EditorialLogic co.edu.uniandes.csw.books.logic.EditorialLogic.java
EditorialLogicTest co.edu.uniandes.csw.books.logic.test.EditorialLogicTest.java

Con el esquema de diseño que tenemos en nuestro proyecto ejemplo, para un modelo conceptual de 10 clases y 8 asociaciones entre ellas, fácilmente tendremos unos 200 artefactos distintos que hay que manejar en solo el proyecto del api y el back y otros tantos en el proyecto web.

Los problemas de integración

En un desarrollo de software donde intervienen varios desarrolladores hay necesidad de dividirse el trabajo. Hay muchas estrategias de división de trabajo: puede ser por componentes, por capas, por conceptos, por requerimientos o casos de uso, etc. El equipo debe definir una estrategia de división y también una estrategia de integración de las partes que cada uno desarrolla. Para la integración, una estrategia posible es esperar a que todos terminen y hacer una fase solo de integración, separada del desarrollo; es decir, cada uno trabaja aisladamente y sólo se relaciona con los otros para integrar. Si bien esta estrategia suena práctica tiene muchos problemas, por ejemplo:

  1. Descubrir tardíamente problemas de entendimiento de los requerimientos de la arquitectura o los diseños: “los pedazos no pegan”.
  2. Corregir defectos en esta etapa, sobre problemas introducidos en etapas anteriores, son más costos y puede crear nuevos problemas.
  3. La calidad del software es difícil de asegurar.
  4. Las pruebas de integración se ven comprometidas porque aparecen muy tarde en el desarrollo.

Todos los problemas anteriores se conocen como Integration Hell donde las consecuencias directas son demoras en las entregas como consecuencia de tener que rehacer trabajo y comprometer la calidad del software.

Una alternativa que ha probado ser muy efectiva para resolver los problemas mencionados y que ahora hace parte de la mayoría de las metodologías de desarrollo de software, es la Integración Continua. Como su nombre lo indica, se trata de ir integrando de manera constante a medida que avanza el trabajo de las distintas partes realizado por los distintos desarrolladores.

Esta práctica nació con la metodología de desarrollo de software ágil llamada eXtreme Programming XP y fue definida por primera vez por Martin Fowler en el artículo Continuous Integration.

Las prácticas de la integración continua

Para que este proceso sea posible, el grupo de desarrollo tiene que definir un ambiente de desarrollo robusto muy estandarizado donde para todos sea claro: la estructura del proyecto, la arquitectura, los esquemas de nombramiento, el depósito central del código donde se hace la integración, el versionamiento, la construcción de los ejecutables, las pruebas para verificar la calidad, etc. Una vez que todos estos elementos han sido definidos en el equipo de desarrollo, tomando como base lo descrito por Fowler en Continuous Integration, las prácticas de la Integración Continua son las siguientes:

  1. Utilizar un depósito para el código central para los artefactos del proyecto
  2. Automatizar la construcción del ejecutable (el “build”)
  3. Ejecutar pruebas automáticamente
  4. Cada integrante hace commit/push cada día
  5. Cada commit debe producir un nuevo build y un proceso de pruebas
  6. Probar en un clon el ambiente de producción
  7. Hacer fácil la obtención de los últimos entregables
  8. Cada uno puede ver los resultados del último build
  9. Automatizar el despliegue

Utilizar un depósito para el código central para los artefactos del proyecto

Además de la organización de los artefactos de software que ya mencionamos antes, es necesario utilizar una herramienta para administrar la evolución de todos estos archivos. Los artefactos evolucionan por varias razones: para corregir, mejorar, adaptar; también pueden evolucionar como variantes que existen de manera simultánea, por ejemplo para un cliente que necesita una alternativa específica de diseño. Hay muchas herramientas que facilitan la administración de la evolución de estos artefactos facilitando mantener la integridad de los mismos. Estas herramientas controlan las versiones de los artefactos manteniendolas de manera organizada en un esquema de nombramiento. También controlan la modificación simultánea de los artefactos por desarrolladores distintos, impidiendo que alguien sobreescriba el trabajo de otro y facilitando la fusión de los cambios. Los comandos básicos de estas herramientas (como GIT y su capa web Github o bitbucket) se muestran en la siguiente figura:

Figura 1
Figura 1

De la figura hay que resaltar que cada uno de los desarrolladores trabaja de manera aislada. Tiene su propio repositorio donde tiene copia del repositorio central y donde puede ir guardando sus cambios. Para esto, clona la primera vez el central y luego hace commits de sus cambios. Cuando un desarrollador decide que su trabajo ya puede ir al repositorio central, realiza un push de sus cambios de tal forma que los demás puedan integrarlos haciendo pull a sus espacios de trabajo locales. Este proceso puede ser mucho más complejo dependiendo de las necesidades del proyecto y de la estrategia de división del trabajo y/o integración que se defina. Por ejemplo, cada desarrollador puede tener su propia rama de desarrollo y en ese caso antes de integrar con la rama común debe haber una verificación del estado de su trabajo. Es decir, debe haber una aprobación para integrar el trabajo en el repositorio común. Además, podría haber más de un repositorio de integración, por ejemplo, integrar primero sobre un repositorio solo para realizar cierto tipo de pruebas, luego un repositorio pre-producción, etc.

El manejo de las dependencias

Todos los proyectos de software necesitan y dependen de librerías desarrolladas por terceros, externos o no a la organización del proyecto, para el desarrollo del proyecto y la construcción del ejecutable. Además no se trata solo de la librería sino que, en muchas ocasiones, se requiere de una versión específica de dicha librería. Por consiguiente, se debe asegurar que cada uno de los desarrolladores del proyecto tiene exactamente las mismas librerías en las mismas versiones requeridas.

Hay varias herramientas de manejo de dependencias, unas más sofisticadas que otras. Cada una de ellas puede manejar librerías desarrolladas en algún lenguaje específico como por ejemplo java, javascript, phyton.

Sin embargo, todas comparten dos cosas en común:

  1. Hay un repositorio, distinto al de las fuentes del proyecto, donde se guardan estas librerías. Si las librerías son de uso gratuito y universal, es decir, quien quiera que las necesite las puede utilizar, estos son depósitos centrales que están disponibles en una URL pública. También puede haber unos depósitos locales o de la organización donde se pueden guardar librerías propias de la organización o del proyecto.
  2. Cada proyecto tiene un archivo donde declara cuáles librerías necesita, hay una forma de identificarlas de manera única, y precisa la versión o versiones de la librería que debe obtener.

En el caso de nuestros proyectos en Java, estamos utilizando como herramienta de manejo de dependencias Maven. Maven es una herramienta creada en 2001 por Jason van Zyl para algunos proyectos de Apache (ver historia de Maven) para administrar la construcción de ejecutables del software siendo el manejo de dependencias una parte de este proceso más general. En el caso del proyecto web escrito en Typescript utilizamos una herramienta llamada npm (que podría significar Node Package Manager, pero hay toda una discusión sobre la sigla aquí).

En esta sección estamos resumiendo las generalidades de las herramientas necesarias para el proceso de Integración Continua. Más adelante tendremos más detalles específicos de Maven y de npm.

Automatizar la construcción del ejecutable

Queremos construir un producto de manera repetible: siguiendo el mismo proceso y utilizando las mismas fuentes, versiones de librerías, datos de config, etc. Si el proceso no está bien definido, puede pasar que cada desarrollador realice un proceso distinto y cree inconsistencias, por razones como las siguientes:

  1. Se usan las librerías o las versiones de las librerías que no son y no estén resueltas las dependencias entre los componentes
  2. No haya certeza sobre cuáles fuentes se empaquetaron
  3. No se haya probado la aplicación que se haya empaquetado
  4. No esté bien configurado el servidor para el despliegue
  5. No se tenga configurada la base de datos

Cada día hay más y más herramientas para los distintos lenguajes y ambientes de desarrollo para dar soporte al proceso que incluya tareas para evitar los problemas anteriores, esto es, herramientas que permiten automatizar procesos como los siguientes:

  1. El descargue de las dependencias necesarias para el proyecto
  2. La compilación las fuentes
  3. La construcción del empaquetado o build la aplicación
  4. La ejecución de cada una de las pruebas definidas en el proyecto
  5. La configuración de el o los servidores para el despliegue
  6. La configuración de la base de datos

En el caso de nuestro proyecto en Java también utilizamos para definir las distintas tareas Maven. La siguiente figura muestra el proceso anterior en la máquina local de un desarrollador:

Figura 2
Figura 2

Adicionalmente hay otra herramienta de Integración Continua, en nuestro caso es Jenkins, que se configura para que automáticamente, responda a un evento, como por ejemplo se hizo un push en el repositorio de las fuentes, y ejecute las tareas de Maven. Es decir, sin intervención manual, se descargan las dependencias, se compila, se empaqueta, se prueba, se lanza el servidor, se despliega, etc.

Entonces, una vez que se ha construido este ambiente, la Integración Continua consiste en que cada desarrollador, de manera ojalá diaria, integre en el repositorio común del equipo su trabajo probado. La herramienta de Integración Continua se encarga entonces de ejecutar las tareas anteriores y de producir un reporte indicando si se pudo hacer la construcción o si hubo algún error, si las pruebas pasaron, si se pudo desplegar, etc.

Esto significa que de manera muy temprana nos podremos dar cuenta si las partes del proyecto desarrolladas por las distintas personas, "pegan" y están correctas. Si eso no es así, rápidamente se podrá detectar el error antes de avanzar. Es decir, el proyecto se va construyendo correctamente de manera gradual.

La figura 3 muestra el proceso que se dispara después de que un desarrollador realiza un push al repositorio central: En este caso Jenkins ejecuta el pipeline (los pasos del proceso, descargar, compilar, probar,... ), se contruye un tablero de control para que se pueda ver el resultado del proceso. Si se realiza el paso de análisis estático en SonarQube, se genera otro tablero de control que contiene las métricas de calidad del producto.

Figura 3
Figura 3
Clone this wiki locally