Continuous Delivery

Resumen de Continuous Delivery


Jez Humble y David Farley en su libro Continuous Delivery (Entrega continua) nos descubren las técnicas y herramientas más exitosas para conseguir un ritmo sostenible a la vez que el mejor time to market.

El libro explica en un lenguaje entendible por técnicos y no técnicos conceptos como gestión de la configuración, integración continua, testing automatizado y pipeline de despliegue. Aunque no sea de lectura compleja, se trata de un libro muy denso (algo más de 450 páginas) y algo repetitivo en algunos capítulos. Sin embargo contiene auténticas joyas como:

  • Si algo duele hazlo con más frecuencia.
  • Trata los scripts y ficheros de configuración igual que el código fuente.
  • Asegura cada commit ejecutando un conjunto de tests automáticos.
  • Hecho significa desplegado en pro. Si no tiene sentido como mínimo en pre.
  • El feedback temprano es vital también en el desarrollo. Como corolario el mejor momento para descubrir bugs es cuando los acabas de introducir.
  • Automatiza las builds. Utiliza los mismos binarios (no regeneres) y los mismos scripts para todos los entornos.

A continuación te comparto mi resumen personal de este gran libro “Continuous Delivery”.

cover

1.El problema de entregar software

Objetivo: entregar software de forma rápida y fiable.

Pipeline de despliegue: automatización del proceso de construcción, despliegue, prueba y release de un software. Cada vez que se hace un commit se crea una instancia de la pipeline donde se comprueba que ese cambio de código, configuración, entorno o bbdd es candidato a release.

3 objetivos: da visibilidad del proceso y mejora la comunicación, mejora el feedback y los errores se detectan tan pronto como es posible, y permite entregar software funcionando en cualquier momento a los usuarios.

Los días de release son estresantes en la mayoría de sitios.

Antipatrones en release:

  • Procesos manuales de despliegue, efectuados por gente capaz pero desmotivada y cansada.
  • Desplegar a entornos preproductivos solo cuando el desarrollo está finalizado. Además los que despliegan en estos entornos lo hacen con la documentación que han hecho los desarrolladores sin conocer el entorno preproductivo.
  • Gestión manual de la configuración en producción. Si tenemos un cluster hay diferencias entre la configuración de los nodos en parámetros, versión de SO y librerías, y los despliegues en staging no garantizan que en producción vayan a funcionar.

Necesitamos que el proceso de release sea automático y frecuente. Automático porque debe ser repetible y sin errores (parte del proceso de construcción), y frecuente para que los deltas sean pequeños, reduzcamos el riesgo y aumentemos el feedback.

3 criterios para el feedback:

  1. Cada cambio tiene que disparar el proceso de feedback. Cada commit debe disparar la compilación, unit tests, cumplir estándares de calidad de código, tests funcionales y no funcionales, tests exploratorios y demo a clientes.
  2. El feedback debe ser lo antes posible. Tendremos diferentes tests en cada fase de la pipeline (fase de commit más rápidos y baratos, y fases posteriores más lentos y que necesitan más recursos).
  3. El equipo debe recibir el feedback y actuar en él. Desarrolladores, testers, operaciones, managers, todos deben compartir dashboards y hacer retrospectivas en cada iteración para mejorar de forma continua.

Principios para la entrega efectiva de software:

  • Proceso repetible de release
  • Automatizar casi todo
  • Todo en control de versiones
  • Si algo duele, hazlo con más frecuencia
  • Crea con calidad
  • “Hecho” significa desplegado en PRO, si no tiene sentido como mínimo en PRE
  • Todos son responsables de la entrega
  • Mejora continua de todo el sistema

2.Gestión de la configuración

La gestión de la configuración es cómo guardas, modificas, y recuperas los artefactos de tu proyecto. Nos permiten reproducir y desplegar la aplicación en N entornos, trazar cambios, modificar la aplicación por parte de cualquiera, etc.

Control de versiones: sistema para acceder a cualquier versión de fichero del proyecto en cualquier momento, y que los equipos colaboren.

Buenas prácticas:

  • Mantén todo lo que necesitas para tu aplicación dentro del control de versiones. No solo código, también librerías, documentos, tests, scripts, informes, etc.
  • Commit regularmente a trunk, mínimo una vez al día y pasar los tests en cada commit
  • Comentar todos los commits con información útil

Las librerías externas deberían estar dentro del control de versiones, o al menos usar un repositorio local tipo Maven, pero no apuntando a Internet o sin las versiones específicas de las librerías.

Los ficheros de configuración deben ir también versionados. No conviene hacer las aplicaciones muy configurables por las siguientes razones:

  • Parálisis por análisis
  • La complejidad del sistema aumenta considerablemente

La configuración no se puede inyectar en tiempo de compilación ni construcción, ya que se deben compartir los binarios entre entornos.

La configuración debe testearse de la misma manera que el código (ej: ping a los servicios, URLs correctas en función de entorno).

Utiliza en la medida de lo posible la misma estrategia de configuración para todas tus aplicaciones. Utiliza buenas prácticas de código también para la configuración, ej: KISS, DRY, nombres significativos.

La configuración del entorno (SO, aplicaciones de terceros, BBDD) debe ser automática también. Esto permite replicar entornos muy rápida y económicamente. Deberías poder replicar tu entorno de producción mediante tu control de versiones.

Es fundamental mantener la configuración de todo en un único sitio y que ésta sea independiente de los binarios de la aplicación. Esta configuración solo se podrá cambiar en el repositorio, incluso en entornos de test. Se debe tratar de la misma forma que el código.

3.Integración continua

La integración continua es la práctica (no herramienta) que consiste en probar la aplicación cada vez que se hace un cambio de forma que la aplicación funcione todo el tiempo. Si el cambio rompe la aplicación el equipo se ocupa de arreglarlo de inmediato.

Requisitos: control de versiones, poder automatizar la build y un equipo que esté comprometido a hacerlo, hacer Checkin regularmente (unas 2 veces al día), tener tests automáticos y que estos no tarden demasiado (entre 1 y 5 minutos), gestionar tu entorno de desarrollo local de la misma forma que cualquier otro entorno.

Existen softwares de CI como Hudson que automatizan el proceso y ofrecen opciones interesantes como mostrar un historial de builds, o lanzar una alarma visual o sonora si ésta falla, gestionar librerías externas, etc.

Prácticas a seguir con CI:

  • No hacer commit en una build rota, ya que será más costoso encontrar la causa
  • Ejecutar los commit tests en local y en verde antes de hacer commit
  • Esperar a que acaben los commit tests y el commit antes de pasar a otra cosa
  • No irse a casa con la build rota, o la arreglas o haces revert
  • Si vas a tardar mucho tiempo en arreglar una build (timebox de 10min aprox) haz revert
  • No comentar los tests que fallan
  • Si rompes tests que no has escrito tú con tu código, es tu responsabilidad arreglarlos
  • Test driven development

Prácticas opcionales pero recomendadas:

  • eXtreme Programming (sobre todo propiedad colectiva del código y refactoring)
  • Hacer fallar la build cuando no se respeta la arquitectura
  • Hacer fallar la build cuando los tests tardan demasiado
  • Hacer fallar la build por romper reglas y estándares de codificación (hacerlo incremental, si con el commit se incrementan las violaciones falla)

Tips adicionales para equipos distribuidos:

  • Utilizar aunque sea un control de versiones distribuido como Git o Mercurial
  • Dividir la aplicación en componentes

4.Estrategia de test

Calidad desde el inicio = construir tests automáticos que se ejecutan durante toda la pipeline.

Los tests no solo dan confianza de que la aplicación funciona, son restricciones para el buen diseño del software y una documentación actualizada.

Tipos de test:

  • Tests de aceptación, prueban todos los criterios de aceptación de negocio en un entorno equivalente a producción como si fuera un usuario. Se automatizan cuando son repetidos más de 2 veces y no suponen un coste excesivo. La cobertura debe ser happy paths y casos puntuales adicionales (no todo).
  • Tests unitarios, prueban partes aisladas de código, usados por desarrolladores.
  • Tests de integración, prueban un conjunto de funcionalidad en conjunto. Sobre todo cuando hay servicios externos y componentes acoplados. Evitar que las transacciones de prueba lleguen al servicio real, bien con un firewall o sustituyendo por un doble. Un doble también ayuda cuando aún no está construido el servicio o no es determinista.
  • Test de despliegue, prueba que la instalación de la aplicación es correcta.
  • Tests en demos, que hacen los usuarios para comprobar que la aplicación hace lo que necesitan.
  • Tests exploratorios, prueban nuevos escenarios planificados y dotan al sistema de nuevos casos de prueba y nuevas iniciativas de mejora.
  • Tests de usabilidad, prueban si el usuario es capaz de obtener valor con la aplicación.
  • Tests beta, el usuario final prueba y da feedback de si algo funcionaría o no.
  • Tests no funcionales, seguridad, capacidad, disponibilidad, (requerimientos no funcionales deben especificarse como criterios de aceptación)

Para poder hacer test si tenemos dependencias podemos usar dobles. Ejemplos de dobles: mocks, stubs, espía, dummy.

Llegar a este ideal de automatización es más simple en proyectos nuevos. Hay que convencer de los beneficios de los tests automáticos a negocio, la calidad debe primar sobre la velocidad a corto plazo.

Cuando se implementa a mitad de proyecto, debemos empezar por los happy path de las funcionalidades críticas que aportan más valor a la aplicación.

En proyectos legacy, lo primero es automatizar la build y después ir creando tests automáticos del código que tocamos. Al mismo tiempo automatizar también las funcionalidades de más valor.

Tener entrevistas con stakeholders al inicio de la iteración para escribir los test cases en Cucumber para que dev team los tenga disponibles al iniciar desarrollo.

Los bugs que se encuentran deben arreglarse inmediatamente. Otra opción es tratar los bugs como features, y solo corregir aquellos cuya resolución aporte más valor que las nuevas features.

5.Anatomía de una pipeline de despliegue

Pipeline de despliegue: Automatización del proceso que lleva el software desde el control de código fuente hasta las manos del usuario. Cada cambio genera una build que pasa por etapas de test desde distintas perspectivas. La build al pasar fases gana en robustez. Si en alguna fase falla la build no sigue promocionando. De esta forma te aseguras que no sube nada a pro sin probar y el desarrollador recibe feedback sobre fallos de inmediato.

Continuous Delivery Pipeline

Fases:

  • Fase de commit: el desarrollador hace un cambio. Se compila, ensambla, ejecuta tests de commit (la mayoría unit tests y alguno de integración) y pasa análisis estático de código. Si el resultado es ok tendremos una release candidate. Si falla ni siquiera se completa el commit. (~10min)
  • Fase de aceptación: automáticamente los mismos ensamblados anteriores pasan tests automáticos de aceptación de usuario, más lentos de ejecutar. (1-2h)
  • Fases posteriores, despliegues a demanda de testers para UAT, de operaciones para ejecutar tests de capacidad y despliegue a producción. Podemos usar herramientas para desplegar con un solo clic en cualquier entorno cualquier versión, ver qué hay desplegado en cada entorno, etc.

Consejos:

  • Construir y ensamblar los binarios una sola vez. Esto obliga a separar correctamente el ejecutable (invariable) de la configuración (varía con el entorno).
  • Despliega de la misma forma en todos los entornos. Mantén la configuración de cada entorno versionadas.
  • Ejecuta un smoke test automático cuando despliegues en un entorno
  • Despliega en una copia de producción
  • Cada cambio en la pipeline debe propagarse inmediatamente. Solo no se propaga un cambio si hay un cambio anterior todavía en curso o si éste rompe la build.
  • Los unit test solo prueban desde la perspectiva del desarrollador. Introduce algún smoke test automático en la fase de commit.
  • Los tests de aceptación deben hacerlos los desarrolladores (no otro equipo aparte), si no tenderán a obviar los resultados de los tests y se acoplarán a la capa de UI.
  • Los tests de aceptación deben estar expresados en el lenguaje de negocio, “enviar pedido” en lugar de “clic en botón de envío”.

Implementar una pipeline de despliegue, se trata de un proceso iterativo:

  • Modela tu flujo de valor y crea un walking skeleton. En la construcción no hagas nada. Si no tienes unit tests crea uno con assert=true. Si no tienes build, crea un “hello world”. Si no tienes tests de aceptación ejecuta un test que compruebe que el texto “hello world” está presente.
  • Automatiza la build y el despliegue. El servidor de CI coge la última versión del código y lo compila. Los binarios se copian a un entorno de staging.
  • Automatiza los tests unitarios y el análisis de código
  • Automatiza los tests de aceptación
  • Automatiza la release

Utiliza métricas que midan globalmente el sistema, no localmente (efecto Hawthorne, se modifica el comportamiento por el hecho de ser observado). El mejor, tiempo de ciclo. Otros, cobertura de tests automáticos o calidad en análisis de código.

Utiliza la teoría de las restricciones para reducir cuellos de botella:

  1. Identifica el mayor cuello de botella de tu sistema (p.e. testing manual)
  2. Explota la restricción. Asegúrate de tener siempre un backlog de tests manuales que hacer y que la gente que hace ese trabajo no haga otra cosa)
  3. Que el resto de gente no esté al 100% para ayudar a esa tarea, p.e. haciendo más tests automatizados para reducir el test manual.
  4. Eleva la restricción, p.e. contratando más testers.
  5. Identifica tu siguiente restricción y vuelve a empezar.

6.Scripts de build y despliegue

Necesitaremos herramientas para automatizar tanto la build como el despliegue en la mayoría de proyectos.

La build consta de 2 partes: el artefacto que debe construir y sus dependencias.

Herramientas que automatizan la build: Ant, MSBuild, Maven, Rake, Buildr, Graddle.

Ten un script por cada fase de la pipeline. Mantén los scripts en el mismo repositorio que el código, donde puedan colaborar operaciones y desarrolladores. Utiliza el mismo script para todos los entornos. Los scripts deben servir para instalar de cero y para actualizar la aplicación. Utiliza la herramienta de packaging standard del SO.

Utiliza un estándar de estructura de directorios como la de Maven.

En los servidores de test y producción solo se pueden modificar mediante procesos automáticos. 3 opciones:

  1. Script que se ejecuta localmente
  2. Script que ejecuta remotamente un agente en cada nodo
  3. Herramienta centralizada que ejecuta en cada nodo los instaladores del SO

Es conveniente incluir tests que prueben la infraestructura y el deploy. P.e. ping a un servidor web, recuperar un registro de bbdd, etc.

Tips adicionales:

  • Usa paths relativos siempre que puedas
  • Elimina los pasos manuales
  • Identifica los ejecutables para saber con qué versión de código fueron creados
  • No hagas commit de los binarios en el control de versiones
  • Ejecuta todos los test antes de hacer fallar la build si alguno falla
  • Incluye smoke tests

7.La fase de commit

Pasos:

  1. Desarrollador hace commit en trunk
  2. El código se compila
  3. Se pasan los unit tests
  4. Se generan report de calidad del código
  5. Se generan los binarios y se guardan en el repositorio
  6. Tareas adicionales previas a la siguiente fase (p.e. migrar datos de ejemplo)

Los pasos se completan aunque haya algún error. El objetivo es encontrar el máximo de errores posible de una vez,  para corregir todo también de una vez.

Algunos principios y ventajas:

  • Provee feedback útil e inmediato. Si se introduce un error, se detecta rápido porque el desarrollador acaba de introducirlo y tiene poco donde buscar.
  • Utiliza los mismos criterios de calidad para los scripts que para el código (modular, simple, mantenible, etc.)
  • Puede haber un equipo especializado de build y despliegue, pero la propiedad debe ser compartida entre este equipo y los desarrolladores.
  • En equipos muy grandes (> 30) es útil tener un rol rotativo de build master, quién se encarga de mantener la build funcionando y las buenas prácticas.

Utiliza un repositorio de artefactos para guardar binarios y reports sobre cada instancia de la pipeline.

Cómo deben ser los tests de la fase de commit:

  • Deben ser rápidos (<5min). Para ello no deben usar bbdd, sistema de ficheros, red, ui, solo dobles de test.
  • Incluye 1-2 smoke tests end-to-end de alto valor
  • Evita la asincronía
  • Utiliza mocks para los unit tests para reducir el esfuerzo de desarrollo
  • Evita que los tests deban guardar o dependan de un estado

 

8.Test de aceptación automático

2 tipos de tests de aceptación:

  1. Funcionales: prueban que se cumplen los criterios de aceptación
  2. No funcionales: comprueban seguridad, capacidad, rendimiento, usabilidad, etc.

Estos tests de aceptación se deben desarrollar entre desarrolladores, analistas y testers trabajando en conjunto.

Fases:

  • Creación de la suite de tests de aceptación: Los criterios de aceptación se deben escribir siguiendo los principios INVEST y con la automatización en mente.
  • Implementación de los tests: Mantén los tests escritos en el lenguaje del dominio. Utiliza herramientas como Concordion, Cucumber o Fitnesse, o al menos escribe los criterios de aceptación directamente en los nombres de los métodos en xUnit.
  • Implementación de la capa del driver de aplicación, que será la que conozca cómo interactuar con la API o UI de la aplicación para ejecutar los tests.

No es recomendable hacer tests de aceptación contra la GUI, ya que cambia muy rápido y es complejo crear escenarios a través de ella.

BDD, Business driven development: escribir los tests de aceptación de manera que se puedan ejecutar contra la aplicación directamente.  De esta forma los requisitos no quedan desactualizados y si alguna versión de la aplicación no los cumple se lanza una excepción.

Criterios de aceptación = given (estado) when (evento) then (resultado esperado).

Utiliza el patrón de diseño window driver para separar la semántica del test de la implementación de la GUI.

Deben ser atómicos = se pueden ejecutar en cualquier orden y el resultado es el mismo.

Incluye algún test de deploy en esta suite y si falla haz fallar la build inmediatamente antes que el resto de tests fallen irremediablemente.

Si una build no pasa los test de aceptación no puede continuar el proceso. El dev team debe hacerse cargo inmediatamente del fallo y corregirlo. Es un acto de disciplina, ya que el commit ya se llevó a cabo.

Si no se implementa correctamente este paso pueden pasar 3 cosas:

  • Se invierte mucho tiempo en identificar y resolver bugs
  • Se hace mucho test manual
  • Se entrega software de mala calidad

El foco de los tests de aceptación no es la velocidad de ejecución, sino la certeza de que el sistema hace lo que se supone debe hacer. Esto implica que estos tests a veces requieran más tiempo para completarse, incluso horas. Esto no quita que estos tests puedan y deban estar optimizados refactorizando, compartiendo recursos y paralelizando.

9.Probando requerimientos no funcionales

Los requerimientos no funcionales principales son:

  • Capacidad: número de transacciones por unidad de tiempo
  • Disponibilidad
  • Seguridad
  • Mantenibilidad

Su caracter transversal hace que deban identificarse y definirse al inicio del proyecto. Además se contraponen (seguridad vs usabilidad, flexibilidad vs rendimiento,  etc.) así que normalmente se trata de tomar una decisión (tradeoff). Se pueden gestionar como historias o como criterios de aceptación transversales.

Evitar optimización prematura, solo optimiza cuando identifiques un cuello de botella. Utiliza patrones, evita los antipatrones y no sacrifiques nunca la legibilidad del código por el rendimiento si no hay una razón de peso para ello.

En los tests de capacidad evitar poner límites ni muy altos (porque diferirán la identificación de problemas) ni muy bajos (porque ante la mínima variabilidad darán falsos positivos). Crea informes con gráficos para analizar manualmente a posteriori la capacidad de una build.

Introduce un paso de test de capacidad en la pipeline, después de los tests de aceptación.  Utiliza algunos tests de aceptación adaptados para simular casos de uso reales. Utiliza para ello una réplica lo mas fidedigna posible de producción.

Dependiendo de dónde sean más probables los cuellos de botella, interesará ejecutar estos tests contra la UI, la API pública o una API privada como un servicio o la BBDD.

Los tests de capacidad se ejecutarán después de los de aceptación. Es posible introducir algunos en los tests de commit.

10.Desplegando y entregando aplicaciones

La estrategia de release es un documento que contiene:

  • Cuantos entornos hay y quién se encarga de actualizarlos
  • La pipeline de despliegue
  • Descripción de las tecnologías que se usan en la aplicación
  • Cómo monitorizar la aplicación
  • Integraciones con otros sistemas de terceros
  • Plan de recuperación de desastres
  • Una estrategia de archivado de la información de producción
  • Estrategia de actualizaciones

El plan de release incluye:

  • Scripts de deploy
  • Smoke tests para probar que funciona
  • Cómo actualizar la aplicación en caliente
  • Logs y monitorización

El entorno de UAT debe ser réplica de producción, mismo SO, mismas versiones de software, misma configuración de cluster (aunque con menos nodos).

Es posible compartir entorno para probar varias versiones de la aplicación o diferentes configuraciones. Asegúrate de que no afectan las unas a las otras. En caso contrario utilizar virtualización.

Asegúrate de tener los entornos de producción,  UAT y de capacidad antes de salir a producción, tenlos preparados como parte de tu pipeline.

2 principios para poder hacer rollback de forma segura:

  1. Hacer backup de la app, datos y sesión antes de la release
  2. Probar el backup antes de hacer cada release

Si puedes redesplegar el entorno con la última versión estable, puede ser mejor idea redesplegar y no tener backup.

Zero downtime release es un tipo de release donde apenas hay corte cuando se hace una release. Se consigue aislando las partes de una release al máximo,  y actualizando las dependencias (recursos estáticos,  servicios,  datos) antes de actualizar la versión de la aplicación.

En el tipo release rojo-verde se tiene una réplica de producción,  staging. Cuando se genera una release nueva se despliega en staging,  y se pasan los tests. Si son OK, se hace un switch para que staging sea producción y viceversa.

Canary release se utiliza cuando se instala una nueva versión en un conjunto separado de servidores y se abre a un conjunto acotado de usuarios para probar si funciona,  si cumple los parámetros de capacidad, y para tests A/B.

Cuando hay un error grave en producción, el fix debe pasar por todo el pipeline como cualquier otro cambio. Estimar el número de usuarios afectados,  probabilidad de que se reproduzca e impacto en la funcionalidad.  Una alternativa es hacer un rollback a una versión anterior.

Entrega continua (continuous delivery) significa llevar cada commit a producción después de pasar por la pipeline. Implica tener tests automáticos que cubran toda la aplicación. Al desplegar tan frecuentemente con cambios tan pequeño reducimos el riesgo. Se puede combinar con canary releasing.

La recomendación para hacer entrega continua con aplicaciones cliente es la de descarga y actualizaciones automáticas desatendidas. Mientras el usuario opera con la aplicación se descarga la última versión, y se instala o bien inmediatamente o bien al volver a arrancarla.

También es recomendable utilizar crash reports para notificar al dev team de los errores en cliente.

Algunos consejos más:

  • Comunicación entre dev team y operaciones desde el inicio del desarrollo
  • Registra todo en cada instalación
  • No borres los ficheros de la versión anterior, muévelos
  • Utiliza warmup y smoke tests tras las actualizaciones
  • No hagas cambios directamente en producción

11.Gestionar infraestructura y entorno

Para mantener bajo control tu infraestructura necesitas:

  1. Que la configuración y estado de tu aplicación esté en el control de versiones
  2. Que se auto restablezcan en caso de fallo
  3. Debes conocer el estado de tu aplicación en cualquier momento mediante monitorización o instrumentación

Es responsabilidad del equipo de desarrollo establecer de qué manera se va a monitorizar la aplicación, e incluirlo en el plan de release.

Es necesario bloquear cualquier acceso manual a los entornos, incluso a la gente de operaciones. También aplica a los entornos previos.

La automatización del servidor se hace con PXE o Windows Deployment Services, donde se descarga una imagen base del SO por Ethernet desde un servidor, se configura con los paquetes a instalar y los parámetros de sistema.

Gestión del middleware:

  • Binarios
  • Configuración, desde ficheros del control de versiones
  • Datos

Usa siempre software que permita gestionar la configuración mediante ficheros o script+API. Utiliza Puppet o similar para recuperar esa configuración del control de versiones y aplicarla en cada nodo.

La virtualización nos permite un nivel más de provisionar entornos. Como ventajas tiene estandarizar máquinas, proveer entornos más rápido,  asociar versión de entorno con versión de la aplicación y paralelizar los tests para resultados más rápido. Es especialmente útil cuando tienes ya un sistema en producción y quieres hacer copias para test. Convierte este servidor de producción en una imagen de disco y replica cuantos entornos necesites.

Una alternativa más es el Cloud:

  • Infrastructure as a Service, control total sobre el SO
  • Platform as a Service, control sobre el middleware y servicios adicionales, sobre todo relacionados con requisitos no funcionales (escalado, cache, etc.)

Monitorizar la infraestructura permitirá obtener información sobre el comportamiento de la aplicación en cualquier momento,  planificar intervenciones,  conocer el uso de la aplicación, y detectar y corregir errores en el momento que se presenten. Para ello es necesario que la aplicación e infraestructura registre los eventos y errores, y que estos se guarden en un repositorio para su análisis.

12.Gestionar datos

Al contrario que el código y la configuración los datos deben preservarse en cada despliegue.

La base de datos se va actualizando mediante scripts que se versionan junto al código en el control de versiones.

El modo más efectivo de versionar la base de datos es:

  • Crear una tabla para almacenar el número de versión
  • Crear un script para actualizar de versión n a n+1
  • Crear un script para actualizar de versión n+1 a n
  • Tener en la configuración de la aplicación la versión de la base de datos con la que es compatible

DbDeploy automatiza la creación y ejecución de estos scripts. Para el despliegue a producción nos pueden pedir que podamos hacer rollback sin perder datos y que no tengamos que parar la aplicación para actualizar.

Se pueden hacer hot deployments migrando aplicación y BD. Primero se actualiza la aplicación a una versión compatible con las BD vN y vN+1, y después se actualiza la BD a vN+1.

Se debe utilizar el patrón repositorio para crear una capa de abstracción entre la lógica y los datos. En los tests unitarios se sustituye la implementación por un doble de la BD en memoria.

Los tests de aceptación deben ser aislados (no dependientes). Con BD la forma más sencilla es utilizar un rollback al finalizar cada test para volver la BD al estado inicial. Otra opción menos recomendada es utilizar particiones lógicas de datos (por ejemplo identificadores que empiezan con el id del test).

Sobre todo en la fase de commit, si necesitas invertir mucho en tener datos de test, suele ser indicador de un mal diseño de la aplicación (alto acoplamiento). Para los test de aceptación la directiva es utilizar los conjuntos de datos aislados y mínimos para que el caso de test pase y nada más. Para cargar estos conjuntos de datos mejor utilizar la API, para probarla en conjunto y para evitar inconsistencias en el sistema cuando se ejecuten los tests.

13.Gestionar dependencias

Sistemas basados en componentes = elementos de software que ofrecen una interfaz con la que comunicarse y que pueden ser sustituidos por otros con idéntica interfaz. Lo contrario es un sistema monolítico.

La propuesta de la integración continua es mantener la aplicación siempre en un estado entregable. Para conseguirlo se necesita:

  • Esconder la funcionalidad hasta que esté finalizada
  • Implementar en pequeños trozos, cada uno entregable de por sí.
  • Usar branch by abstraction para implementar grandes cambios en el código
  • Usar componentes para desacoplar partes del código. No separes los equipos por componentes, es mejor hayan equipos por tema, que todos sepan de todos los componentes, e ir rotando la gente por los equipos. La ley de Conway dice que la aplicación es el reflejo de cómo funcionan las comunicaciones entre equipos, así que cuidado cómo diseñas tus equipos.

Las librerías son dependencias en tiempo de compilación. Los componentes son dependencias en tiempo de ejecución y cambian con más frecuencia.

Para gestionar las dependencias de librerías utilizar un software tipo Maven o Ivy que resuelva las dependencias desde un repositorio local con Artifactory.

En la medida de lo posible mantener en una sola build toda la aplicación. Solo separa en una build aparte un componente cuando ralentice en extremo la pipeline o que hayan componentes que cambien a diferente ritmo.

Si tienes N componentes desarrollándose a la vez, utiliza la pipeline para construir y validar tantas versiones de la aplicación integrada como pueda. Si hay muchos cambios a la vez y pruebas con menor frecuencia,  te será difícil encontrar errores.  Otra forma es cuando detectas un error construir tantas combinaciones como sea posible con componentes”verdes” hasta que encuentres el que falla.

Si tienes que implementar cambios complejos en un componente que romperán alguna dependencia, puedes utilizar una rama de versión estable mientras desarrollas lo nuevo en trunk. Los problemas de integración deben ser menores si separamos por componentes.

Cuando utilices librerías de terceros no aceptes cambios de versión a menos que necesites una feature o te quedes sin soporte.

Evitar siempre las dependencias circulares. Si no se puede evitar incrementar las versiones de ambas en cada build (coger siempre la última versión x del componente A para contruir la versión y de B, entonces coger esa versión y para construir x+1, entonces coger x+1 para construir y+1, etc.).

Utiliza un repositorio de artefactos (librerías). Puedes usar una estructura de directorios con una carpeta para cada pipeline y dentro una carpeta con el identificador de cada versión generada como nombre y todos los artefactos de esa versión dentro. La pipeline debe usar el repositorio para guardar las librerías generadas, guardar los informes de los tests, y recuperar estas librerías en etapas posteriores.

14.Control de versiones avanzado

3 razones para hacer branch:

  1. Sacar una versión release
  2. Sacar una rama de refactor o spike
  3. Cuando tienes que hacer un cambio grande y no hay forma de no afectar al resto de desarrollos

Usar optimistic lock (no bloquear ficheros si más de un desarrollador van a tocarlo) siempre para acelerar el desarrollo.

Se pueden hacer ramas mainline, integración y release y que solo los testers u operaciones puedan hacer merge (promocionar) a las ramas de release.

Cuando 2 ramas hacen merge y tienen implementaciones distintas en la misma sección de código se produce un conflicto.  Esto requiere que los 2 autores hablen y decidan como dejar el código de esa sección, probablemente semanas después de haberlo escrito. Se puede pactar hacer merge con más frecuencia, cada día por ejemplo.

Si estás usando ramas y no integras en trunk al menos una vez al día entonces no estás haciendo integración continua. La estrategia que se recomienda es desarrollar en trunk y hacer ramas por release. Es posible hacer esto también con equipos grandes. Si pasan demasiado tiempo haciendo merge con integración continua es que el código no está bien estructurado y dividido en componentes.

Otras estrategias son:

  • Rama por release, para aplicar hotfixes. Cuando se sacan releases con cierta frecuencia deja de tener sentido.
  • Rama por feature, cada historia en una rama. Hay que hacer pull cada día para traerse los cambios de trunk a cada rama. Las ramas no deberían vivir mucho tiempo. No se debería hacer merge de nada que no hayan probado los testers. Hay tech leads que se preocupan de que trunk sea entregable en todo momento. Funciona sobre todo en proyectos open source, donde hay un grupo pequeño de desarrolladores que controlan lo que se mergea a trunk. Especialmente peligroso cuando se combina con fechas de entrega, desarrolladores con poca experiencia y equipos de test separados de desarrollo.
  • Rama por equipo, los mismos inconvenientes que la anterior y además no puedes integrar solo un bug o feature en trunk, siempre integras toda la rama o nada.

15.Gestionar la entrega continua

La entrega continua ayuda a mantener un nivel sostenible de desarrollo y entrega a la vez que reduce el riesgo y aumenta el feedback tanto a negocio como a desarrolladores.

Utiliza el siguiente modelo de madurez para evaluar y evolucionar tu proceso:

Gestión de builds e integración continua Entornos y despliegue Gestión de entregas y conformidad Testing Gestión de los datos Gestión de la configuración
Nivel 3 – Optimizando
Foco en la mejora de proceso
Equipos se reúnen regularmente para discutir problemas de integración y resolverlos con automatización, feedback más temprano y visibilidad Todos los entornos gestionados eficazmente. Aprovisionamiento automatizado. Operaciones y desarrollo colaboran para gestionar riesgos y reducir tiempo de ciclo Rollbacks de producción infrecuentes. Los defectos que se encuentran se arreglan de inmediato. Feeedback sobre el rendimiento de la base de datos y proceso de despliegue entre entregas. Validación frecuente de que las políticas promueven la colaboración, el desarrollo ágil y un proceso de gestión de cambios auditable.
Nivel 2 – Gestionado en gran parte
Proceso medido y controlado
Métricas sobre las builds visibles y gestionadas. Las builds no quedan rotas mucho tiempo. Despliegues a los entornos gestionados. Procesos de entrega y rollback probados. Salud del entorno y la aplicación monitorizada y gestionada al igual que el tiempo de ciclo. Métricas de calidad y tendencias medidas. Requerimientos no funcionales definidos y medidos. Actualizaciones y rollbacks de base de datos probados en cada despliegue. Rendimiento de la base de datos monitorizado y optimizado. Desarrolladores hacen commit a trunk al menos una vez al día. Las ramas se usan solo para las entregas.
Nivel 1 – Consistente
Procesos automatizados en todo el ciclo de vida
Build automática y tests automáticos pasados en cada commit. Dependencias gestionadas. Scripts y artefactos reutilizados. Despliegue a cualquier entorno totalmente automatizado. Procesos de gestión de cambio y aprobaciones definido y aplicado. Se cumplen con las condiciones regulatorias de conformidad. Unit tests. Tests de aceptación escritos con testers. El testing forma parte del desarrollo. Cambios en la base de datos se aplican como parte del proceso de despliegue. Librerías y dependencias gestionadas. Políticas de uso del control de versiones en el proceso.
Nivel 0 – Repetible
Proceso documentado y parcialmente automatizado
Builds automáticas frecuentes. Cualquier build se puede recrear mediante el control de versiones y un proceso automático. Despliegue automático a algunos entornos. Crear nuevos entornos es trivial. Configuración versionada. Entregas fiables pero infrecuentes y dolorosas. Trazabilidad limitada de los requisitos a la entrega. Tests automáticos escritos como parte del desarrollo. Cambios en la base de datos mediante scripts versionados con la aplicación. Control de versiones con todo para recrear el software: código, configuración y scripts de build, despliegue y datos.
Nivel -1 – Regresivo
Procesos no repetibles, descontrolados y reactivos
La build es un proceso manual. Los artefactos no se gestionan. Proceso manual de despliegue. Binarios propios de cada entorno. Entornos también manuales. Entregas no fiables e infrecuentes. Testing manual después del despliegue. Cambios en la base de datos manuales y sin versionar. Sin control de versiones o usado parcialmente.

 

Cualquier iniciativa software debe tener las siguientes fases:

  1. Identificación del caso de negocio (qué valor buscamos crear), los stakeholders y el sponsor principal o product owner.
  2. Inception donde se define el caso de negocio y se definen requisitos alto nivel y se crea un primer plan y alcance. Todo lo creado en esta fase debe estar abierto a cambios.
  3. Iniciación donde se crea el equipo y sus acuerdos de trabajo, instalan máquinas, servidores, y un “hello world”.
  4. Desarrollo y release, preferencia por un modelo iterativo como Scrum.
  5. Operación al terminar una primera release.

Durante todo el ciclo también será necesaria una gestión de riesgos. Para identificarlos pregúntate cosas como cada cuánto enseñas el software al cliente, cómo gestionarás los defectos, cómo gestionarás la configuración o cada cuánto harás releases.

Hasta aquí el resumen del libro. Si te ha gustado y estás pensando en comprarlo, puedes hacerlo desde el siguiente enlace de afiliados. A ti no te cuesta nada y a mí me ayudarás a mantener este blog.

También te puede interesar...

Cómo preparar una Sprint Review (Review Series II)... En el primer artículo de la serie vimos algunos antipatrones de Sprint Review comunes que restaban efectividad a esta sesión tan importante de Scrum. ...
Claves para una Sprint Review efectiva (Review Ser... La reunión de Sprint Review es en la práctica la única oportunidad que tienen muchos equipos Scrum para interactuar con sus clientes y usuarios. Norma...
Resumen Agile Retrospectives de Esther Derby y Dia... El libro Agile Retrospectives de Esther Derby y Diana Larsen es un manual para Scrum Masters sobre cómo facilitar las retrospectivas en los equipos. ...

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *