Contract Testing Workflows
23 de febrero de 2023
¿Sobre qué trata este artículo?
Este es el primero de una serie de artículos que publicaremos desde Sngular para tratar los procesos y configuraciones más comunes que deberás recorrer en tu camino hacia PactFlow Enterprise.
Vamos a abordar el primer tema que le viene a la mente a cualquier cuando se inicia el proceso de adopción de contract testing en un entorno empresarial: Los flujos de trabajo.
A estas alturas, seguramente ya sepas qué es el contract testing, hayas estado jugando con una PoC (prueba de concepto) e incluso puede que hayas desarrollado algún MVP (producto mínimo viable). Tras estos ejercicios introductorios, es el momento de definir los flujos de trabajo que se aplicarán en escenarios reales. Es en este punto exacto donde las cosas empiezan a complicarse un poco más, y vamos a compartir contigo algunos consejos que hemos aprendido por el camino.
En primer lugar, es necesario aclarar que cada entorno empresarial es diferente. Probablemente tendrás un montón de pipelines distintos para cubrir el proceso de CI/CD, con situaciones muy específicas. En éste artículo vamos a intentar resumir algunos de los conceptos generales que deberías tener en cuenta. Pero, por supuesto, estos conceptos tendrán que ser adaptados a cada situación particular.
¿Por qué es interesante?
Este artículo cubrirá los casos de uso básicos que son necesarios para implantar contract testing con éxito. Se pueden utilizar como referencia para tener la seguridad de estar cubriendo al menos los flujos de trabajo más fundamentales. Pero ten en cuenta que tal y como se ha comentado antes, tendrás que adaptarlos a tu realidad.
La información incluida en estos flujos de trabajo intentará ir un paso más allá de los típicos artículos introductorios o demos. Se da por supuesto que tienes un conocimiento básico de la metodología.
Aunque si te apetece profundizar más en temas de configuración y definición de pipelines, recuerda que tenemos un artículo complementario donde hablamos sobre cómo configurar el CI/CD, y también este otro artículo (más denso) con "trucos" y consejos para tus escenarios reales.
Contexto
Como sabrás, existen dos metodologías principales a la hora de hablar de contract testing:
- Consumer Driven: El flujo lo inician (y gestionan por completo) los consumidores. Los proveedores deben validar el contrato, lo que los obliga a incluir código en su fase de pruebas. Consulta este artículo si quieres más información.
- Bi-direccional: Para situaciones en las que no hay control sobre el código del proveedor (o bueno, quizás sí lo haya… pero no hay voluntad de modificarlo). También tenemos publicados algunos artículos relacionados.
Bi-direccional y consumer driven son mucho más parecidos de lo que en un principio puede parecer, hay una ligera diferencia en cómo se gestiona la parte del proveedor (por razones obvias), pero los flujos de trabajo en bi-direccional podrían entenderse como un subgrupo de los flujos de trabajo definidos para consumer driven. En este artículo, nos centraremos en el enfoque consumer driven. Cubrir ambos implicaría crear un artículo demasiado largo, por lo que nos guardamos para el futuro el detallar esas diferencias.
Por nuestra parte recomendaremos utilizar consumer driven siempre que sea posible, ya que consideramos que es la metodología que realmente explota todo el potencial de este framework. Y está claro que todos tenemos en mente situaciones y productos con los que no se podría seguir ese enfoque. De ahí que sea una muy buena noticia disponer del bi-direccional como alternativa , pero desde nuestro punto de vista debe tratarse como tal: una alternativa. No la implementación por defecto.
Flujos de trabajo
Primera ejecución - Consumer Driven
Todo debería arrancar con una reunión (sí, sé que estabas pensando que utilizando contract testing nos libraríamos de las reuniones, ¿verdad? Bueno, digamos que es una verdad a medias... tendrás menos, pero desgraciadamente nadie te va a librar de las primeras).
Ambos equipos tendrán que presentar sus necesidades, con el objetivo de asegurar que la comunicación entre los dos sistemas sea correcta. Tendrán que ponerse de acuerdo en las solicitudes y respuestas esperadas para cada uno de ellos. Es bastante probable que tus equipos no estén partiendo de cero, si ese es el caso, simplemente podrías usar como punto de partida las versiones que están utilizando en ese momento y que (esperemos…) están funcionando correctamente. Una vez alcanzado el acuerdo, el equipo del lado del consumidor debería generar un contrato con la especificaciones necesarias y publicarlo en PactFlow lo antes posible. A partir de ese momento, los dos equipos pueden trabajar de forma independiente (¡sí, se acabaron las reuniones!)
Ambas partes estarán sincronizadas a través de los pipelines y los webhooks de PactFlow. Serán notificadas constantemente sobre los cambios, el estado de los contratos, y lo más importante: si es seguro desplegar. Si un contrato no está verificado, el componente en cuestión será bloqueado y no podrá realizar su despliegue.
Ten en cuenta que en tu primera ejecución como consumidor, después de publicar tu contrato, “can-i-deploy” bloqueará el despliegue. Es normal: el proveedor no está desplegado, por lo que tu contrato aún no puede estar verificado, así que el framework no puede permitirte el despliegue. Por eso es necesario ejecutar el CI del proveedor para validar esa primera versión del contrato y de paso registrarlo como desplegarlo en el entorno de destino.
Esta es una situación que te encontrarás en cualquier despliegue que contenga un cambio que afecta a la comunicación, tu consumidor se verá obligado a esperar a que el proveedor incluya los cambios por su parte y verifique tu nueva definición de contrato.
Disclaimer! → En los diagramas, se representan los CI como líneas de tiempo. Pero eso no implica que tenga que ser la misma ejecución del pipeline y que tengas que pausarlo esperando una respuesta (como ocurriría con muchos webhooks de retorno).
Consumidor - Despliegue sin impacto en la comunicación
Una situación bastante común, podría ser un simple hotfix o una nueva versión del consumidor. La cuestión es que este quiere actualizar su servicio en un entorno donde ya está desplegado. Los cambios incluidos teóricamente no tienen impacto en la comunicación, pero debemos asegurarnos de ello.
Tras haber incluido los nuevos cambios, la verificación del contrato resultante se realizará contra la versión del proveedor existente en el entorno donde el consumidor quiere ser desplegado. Si la verificación tiene un resultado positivo, la nueva versión del consumidor puede desplegarse con seguridad.
Proveedor - Despliegue sin impacto en la comunicación
Este escenario es equivalente al punto anterior, pero desde el punto de vista del proveedor. Esperamos que nuestros cambios no tengan un impacto en la comunicación con el consumidor, y el equipo se asegurará de ello siguiendo un proceso similar al siguiente.
Consumidor - Despliegue con cambios en la comunicación
Este caso prácticamente idéntico al caso base relacionado con la primera ejecución de consumer driven. De hecho no incluiremos diagrama porque sería duplicar el mismo.
La situación que nos atañe es la siguiente: el consumidor ha creado una nueva versión de contrato, diferente a las que tenemos ya verificadas en PactFlow. Además, la nueva versión de contrato no es compatible con las versiones de proveedor que están actualmente desplegadas. Por tanto, se bloqueará el despliegue de la nueva versión del consumidor hasta que tengamos una versión del proveedor compatible. Durante el despliegue de esta nueva versión del proveedor, el contrato (antes incompatible) será verificado para esa versión en particular.
En el momento en que esa nueva versión compatible del proveedor esté disponible en el entorno donde queremos desplegar el consumidor, se desbloqueará el proceso. Ten en cuenta que, aunque el diagrama muestra este proceso como secuencial, podrían pasar días o incluso semanas entre algunos de los pasos. Los equipos trabajan de forma asíncrona. La parte positiva es que PactFlow nos permitiría, mediante webhooks, notificar al consumidor de que su contrato ha sido verificado, automatizando de forma completa todo el proceso.
Proveedor - Despliegue con cambios en la comunicación
Ahora veamos la situación contraria, un proveedor quiere incluir algunos cambios que causarán un problema en la comunicación con uno de sus consumidores. El framework detendrá el despliegue y se evitarán errores en entornos avanzados.
Siendo estrictos, los desarrolladores deberían ser conscientes de este problema mientras trabajan en sus equipos. Trabajando en local, el framework descargará la última versión del contrato para realizar las validaciones en el proveedor (última versión por defecto, aunque por supuesto, podrías apuntar a una versión o entorno específico). Pero puede ocurrir (y ocurrirá) que la versión desplegada no sea la más reciente.
Aquí la situación es un poco más complicada que en el lado del consumidor. Cuando el proveedor intenta desplegar un “breaking change”, será bloqueado. Pero si el consumidor intenta crear un nuevo contrato compatible con la futura versión del proveedor también será bloqueado (ya que esa nueva versión no será compatible con la versión actualmente desplegada del proveedor).
Para solucionarlo, los pasos a seguir son:
- El proveedor tendrá seguir la estrategía de “expandir y contraer”, incluyendo de forma incremental los cambios.
Si hablamos, por ejemplo, de un cambio de nombre en un campo (cambio de ‘name’ a ‘firstName’ por ejemplo), necesitarás incluir en una primera versión del proveedor ambos valores para dicho campo, el antiguo y el nuevo, para mantener la compatibilidad con el consumidor. - El consumidor puede ahora desplegar la nueva versión del contrato en la que sólo utiliza ‘firstName’ (porque ya está soportado por el proveedor).
- El proveedor puede crear una nueva versión y deshacerse del antiguo campo de ‘name’, ya que no es utilizado por ningún consumidor.
Breaking Changes
Aunque hemos visto cómo gestionar los cambios con impacto desde el lado del consumidor y del proveedor, es importante aclarar la ventaja que ofrece el enfoque consumer driven cuando se trata de evitar errores, y sobre todo los temidos “breaking changes” por sorpresa.
Se trata de una metodología que pone el foco en el consumidor, cualquier cambio en la API de un proveedor debería (o mejor dicho: debe) originarse en base a una necesidad del consumidor. En esta estrategia se defiende que debemos intentar evolucionar un poco y abandonar los viejos tiempos en los que una API cambiaba su estructura sin preocuparse por sus consumidores ("tendrán que estar ellos al tanto de mis cambios, y si no, ya se darán cuenta…").
Aunque podemos aceptar que aún tiene sentido en algunas situaciones específicas, en un escenario moderno de arquitectura de microservicios, creo que todos podemos acordar que no debería ser el camino a seguir.
Pero no te preocupes, también tenemos cubiertas esas "situaciones específicas". Quizá estés consumiendo una API de terceros sobre la que no tienes control, o quizá exista en tu empresa la típica API enorme con cientos consumidores, que no va a aceptar desarrollar pruebas de contrato. Para ese tipo de situaciones, existe el enfoque bidireccional. Pero (al menos en mi opinión) debería gestionarse siempre como una alternativa. La excepción, no la regla. El verdadero potencial del contract testing está en manos del consumer driven.
Estrategias de trabajo
En esta sección se intentarán recoger consideraciones básicas para trabajar con PactFlow y contract testing. Ten en cuenta estos consejos al diseñar tus integraciones.
Probar primero en local
Bastante obvio, pero extremadamente importante.
Los equipos de desarrollo deben tener acceso a PactFlow (pero recuerda: acceso de sólo lectura, tu herramienta de automatización debería ser la única con permisos de escritura). Habilitándoles el acceso, podrán desarrollar y ejecutar pruebas localmente, antes incluso de subir el código al repositorio.
Se trata de una funcionalidad súper potente, ya que convierte el feedback sobre contract testing en instantáneo, lo que evita descubrir estos problemas en fases posteriores del desarrollo.
Si los desarrolladores se familiarizan con el framework, podrán probar la compatibilidad de su código con cualquier entorno, etiqueta o versión específica del contrato. Y de verdad que eso es un “game changer”, el tiempo invertido en identificar o reportar errores durante el E2E o incluso durante las pruebas funcionales se reducirá drásticamente.
Aplicar contract testing en todas las ramas del repositorio
Todos estos diagramas que hemos visto deben ser incluidos a nivel global. Eso significa que no sólo se aplican en main, si no en todas las ramas de desarollo (feature, bugfix, etc…)
Si las pruebas fallan, se podría evitar el integrar el código en la rama principal e incluso bloquear despliegues en entornos de DEV si se considera necesario (teniendo en cuenta que estos despliegues se hacen desde ramas feature por lo general).
Habilitar pending pacts
Se trata de una funcionalidad fundamentaly por desgracia… bastante confusa. Vamos a tratar de aclarar su propósito.
Tener habilitado el flag “enablePending” asegurará que la build del proveedor sólo falle cuando esté rompiendo un contrato ya verificado.
Partamos de la base de que normalmente durante la build de un proveedor, se validarán todas las versiones desplegadas de un contrato así como la última.
Imagina que tienes la versión 1.0 de tu consumidor en PROD. Es compatible con su proveedor, y todo funciona sin problemas. Ahora el consumidor ha comenzado a trabajar en 2.0, y esa versión requiere un nuevo parámetro, lo que significa que no es compatible con el código actual del proveedor.
El contrato del consumidor 2.0 será generado (desde su rama feature) y el contrato será publicado en PactFlow. Cuando tu proveedor intente construir su código, intentará validar ambas versiones del consumidor (1.0 porque es la desplegada, 2.0 porque es la última), si pending pacts no está habilitado, fallará la build. Esta situación bloquearía al equipo del proveedor, impidiéndoles trabajar en nada que no fuese ese cambio específico necesario para el consumidor 2.0.
Al habilitar los pactos pendientes, le estás diciendo a tu proveedor que sólo rompa la compilación si el fallo en la verificación es contra un contrato ya verificado (consumidor 1.0 en nuestro caso).
Llegados a éste punto quizás te estés preguntando "¿para qué quiero validar la última versión de un contrato? Si elimino esa validación, no necesitaría en absoluto la función de pending pacts". La respuesta sencilla: Necesitas esa validación de la “ultima versión disponible” para poder llevar adelante CUALQUIER actualización de un contrato. El consumidor no podría nunca desplegar sino se intentar verificar sus contratos “no desplegados”, y entraríamos en un bloqueo.
Incluir esta configuración habilitada por defecto es algo que está sobre la mesa desde hace tiempo pero que no se ha hecho todavía (al menos en el momento de publicar este artículo). El motivo es que este comportamiento, tan clave en una implementación en mundo real, es bastante contraintuitivo si estás haciendo una PoC o demo, y por tanto se decidió dejarlo desactivado por defecto.
Verificaciones de amplio espectro
WIP, que significa "work in progress", hace referencia a una característica bastante útil de PactFlow que recomendamos incluir en los pipelines. Antes de nada, decir que este tipo de temas se tratan con mucha más profundidad en el artículo “Contract Testing & CI”, pero como adelanto, decir simplemente que el CLI de Pact ofrece la posibilidad de verificar no sólo el contrato que ha disparado la verificación, sino todos los contratos que quieras. Personalmente recomendaríamos validar todos los contratos desplegados en algún entorno, así como la última versión disponible del contrato (desplegada o no).
Este enfoque simplifica mucho todos los flujos de trabajo, y desacopla los pipelines de verificación. Y aún más, te ahorrará tiempo la ejecución de los pipelines de entornos superiores, ya que la verificación ya estaría hecha al llegar allí. Pero no quiero enredar más este artículo, a si que si te interesa profundizar en temas como este… ya sabes: Contract Testing & CI.
Crear un GoP (Grupo de Práctica)
Aunque no está directamente relacionado con el trabajo técnico a realizar, ayudará mucho a tener éxito en la adopción de la metodología, por lo que creemos que merece la pena mencionarlo al menos brevemente.
El GoP (Grupo de Práctica) es un “departamento” que estaría compuesto por diferentes roles organizativos. Su principal función sería guiar, apoyar y hacer un seguimiento continuo del grado de adopción del contract testing en de la organización. Su importancia es crucial, ya que ayudará al equipo técnico a llegar a todos los equipos y departamentos implicados en este proceso.
Para simplificar, estaría compuesto por al menos el SME (subject matter expert) de Contract Testing, el product owner o manager del GoP y cualquier stakeholder del proyecto que pueda ayudar a mover adelante la adopción de la metodología. Piensa que necesitarás (y mucho) la colaboración de todas las partes interesadas para tener éxito cuando intentes implantar una metodología como ésta en una gran organización. Va a implicar trabajar con un montón de departamentos diferentes sólo para la parte de plataforma y CI/CD y, más adelante, colaborar con absolutamente todos los equipos de desarrollo en el proceso de adopción.
Puedes encontrar más información al respecto en la documentación de PactFlow. Y si tienes tiempo podrías también ver este vídeo que tenemos en colaboración con el equipo de PactFlow. En él desarrollamos al completo el caso practico de implantación de esta metodología en una gran institución financiera de USA.
¡Hasta pronto!
Bueno, antes de nada... si has leído hasta aquí, ¡muchas gracias! El artículo es un poco denso y muy teórico, pero de verdad esperamos que te resulte de utilidad si estás comenzando tu viaje a PactFlow Enterprise.
Recuerda que hemos creado un artículo “índice” donde se listan los diferentes entradas que hemos publicado relacionadas con PactFlow y Contract Testing. A sí que no lo pierdas de vista, ya que nuestra idea es actualizarlo de forma regular 😉