Testes de Contratos com PACT Framework #4 - Pact com CI

No items found.
10/9/2020
Vinícius Ribeiro
Vinícius Ribeiro
Tech Lead

Entusiasta de tecnologia e desenvolvimento de soluções corporativas de alta performance.

Está sem tempo para ler? Aperte o play para escutar o artigo.

No artigo anterior, sobre testes de contratos, usamos o PACT para validar uma fluxo de integração REST entre duas aplicações escritas em Java e SpringBoot (Provider e Consumer).

Leia os artigos desta série:

- Testes de Contratos com Pact #1 - Conceitos

- Testes de Contratos com Pact, #2 - Consumer Driven Contract 

- Testes de Contratos com Pact #3 - Hands-On

Neste artigo, explicarei como integrar todos os passos da validação de contrato e como facilitar as validações. Aqui usaremos conceitos de DevOps e CI para que tudo funcione como o esperado.

O que vamos precisar?

  • JDK Java 11 ou superior instalada
  • IDEs (aqui uso Intellij e VsCode)
  • Docker e Docker Compose instalados

O novo desafio

Vamos utilizar o mesmo cenário anterior de integração REST para facilitar o exemplo. Agora vamos refletir, o que ele tem de errado? O que pode ser melhorado nele? E como o CI ajudaria?

Imagine um cenário real, em que uma organização possui diversas integrações de sistemas, e as equipes adotam o PACT para validar suas integrações, utilizando o processo manual de verificação. Vamos listar as dificuldades que irão ocorrer:

Não temos certeza que a outra parte da integração irá validar nosso contrato

  • Não saberemos quando algum dos lados mudar a integração, se não rodamos toda a validação entre Consumer x Provider
  • Teremos de sempre notificar nossas integrações manualmente de novas atualizações para que elas façam as correções necessárias


Um equívoco comum que ocorre em equipes que estão desenvolvendo uma solução em software, é negligenciar a importância dos testes de integração. Pode ser que no começo eles o implementem, mas depois, não os mantenham, pois o veem como desperdício de tempo. Apesar disso, sabemos que com o tempo será provado que a falta deles o deixará em um ambiente nebuloso quando tivermos que garantir que nossas integrações estão realmente funcionando, ou que todos nossos consumidores tenham alterado suas pontas de integração para consumir de forma correta, quando alteramos alguma delas. 

Ou seja, da maneira anterior, iríamos talvez sofrer pelo cansaço de realizar todo processo manualmente. Com uma ferramenta de CI, podemos executar um fluxo de testes nas integrações, para garantir que elas estão coesas. Podemos ir além, se quisermos, e incluir webhooks para avisar caso algo não esteja conforme esperamos.

Neste artigo utilizarei o Jenkins para criar um fluxo de validação das minhas integrações. Demonstrarei também como ele pode nos ajudar a garantir nossas integrações quebrando algumas delas e mostrando o resultado no build do Jenkins.



Na imagem acima, criaremos builds interligados e dependentes, quando o consumer gerar seu contrato, ele já publicará no broker do PACT, em seguida rodará o build de validação de contratos do Provider. Depois, no Consumer, rodará a ferramenta can-i-deploy, para obter o resultado da integração no broker do PACT, garantindo que a integração está feita corretamente.

Do outro lado, o mesmo processo acontece: quando o provider publicar uma nova versão, validará se suas alterações de alguma forma não quebraram as integrações de seus consumers.

Para isso funcionar, dividi o Consumer e o Provider em dois repositórios públicos:

Os dois projetos possuem seus arquivos JenkinsFile, que executam os stages do Jenkins. No repositório principal, incluí o Jenkins nesse link.

A seguir, apresentaremos os tópicos de CI com Jenkins em três cenários.


O primeiro cenário 

Neste primeiro cenário, será demonstrado um Fluxo de CI que realiza a integração de forma feliz (perfeita) entre nossas implementações de Consumer e Provider.  Também nele, veremos o fluxo completo sobre a perspectiva da experiência do desenvolvedor.



Jenkins Jobs

Na continuação do entendimento do objetivo da PoC, devemos criar Jobs no Jenkins para conseguir rodá-los em sua interface. 

Aproveito para lembrar que o projeto principal que inclui a integração com o Jenkins pode ser visto neste link (pact-ci-workshop).

Para iniciar a análise do pact-ci-workshop, vamos inicialmente ao docker-compose.yml:

<p> CODE: https://gist.github.com/vinirib/25696665819efb821ae88f13b50710e0.js</p>

O docker-compose mostrado acima, descreve basicamente dois componentes, uma imagem de container do PostgreSQL na versão 10.5 e outra imagem, a principal, de container Jekins.

Sobre a imagem do PostgreSQL, o container é basicamente a configuração de acesso como portas, rede, usuário e senha, e a definição do schema de dados padrão, chamado de postgres, que será usado pelo Jenkins para orquestrar seu fluxo de trabalho.

Note que, para a imagem do Jenkins, não é usada uma imagem de container diretamente, mas indicado um Dockerfile, que o configura da forma apropriada para nossa PoC.

No código do repositório pact-ci-workshop, pode ser encontrado o arquivo ./pact-ci-workshop/jenkins_config/Dockerfile:

<p> CODE: https://gist.github.com/vinirib/195792babc43a61cb3846d37273e6d1c.js</p>

O Dockerfile acima, descreve de início, que deve ser utilizada a imagem base jenkins na versão 2.230 com Java 11, seguindo com a configuração de acesso básica (admin/admin), e a parte mais importante são os arquivos copiados (volumes), que são os scripts groovy, que fazem a mágica da integração acontecer.

São eles: 

  • create-jobs.groovy
  • default-user.groovy
  • executors.groovy
  • jenkins-theme.groovy
  • jobs.groovy

Dentre esses, o script responsável por atingir nosso objetivo de integração é o jobs.groovy

O Início do jobs.groovy é feito descrevendo os repositórios alvos do Consumer e Provider, assim como seus respectivos AppNames conhecidos pelo Pact Broker.

Na sequência, são descritos quatro Pipeline Jobs para o Jenkins:

  1. 1-PACT-FLOW-CONSUMER-GENERATE-AND-PUBLISH-CONTRACT
  2. 2-PACT-FLOW-PROVIDER-TEST-CONTRACT-client-api
  3. 3-PACT-FLOW-CONSUMER-CAN-I-DEPLOY
  4. PROVIDER-CHANGED-CONTRACT-CHECK-INTEGRATION

Nota sobre cenário de PoC:

Para cenário de PoC, nosso pipeline do Jenkins é ativado manualmente. O motivo é que, quando subirmos o jenkins em nosso container docker local, o github não terá acesso a esse container. Em um cenário produtivo, a implementação correta seria que para cada deploy na branch produtiva se disparasse o gatilho do pipeline Jenkins para a validação dos contratos. 

Agora, vamos explicar o que cada uma delas faz para que o fluxo de CI aconteça junto ao Pact.

PACT-FLOW-CONSUMER-GENERATE-AND-PUBLISH-CONTRACT 

Esse Job do Jenkins é responsável por baixar o código fonte do repositório consumer-api, rodar os testes unitários (onde se encontram também as expectativas de consumo de contrato PACT) e caso build do projeto seja bem sucedido, publicar o contrato no nosso container PACT que estará disponível na porta 80. Para a publicação do contrato, também enviamos algumas variáveis de ambiente do Jenkins para versionar corretamente o contrato. A tag é o mais importante delas, já que a usamos para sempre pegar a última validação de contrato daquela tag, para verificarmos a saúde da integração.

Jenkinsfile-generate-and-publish-contract

Esse é o arquivo JenkinsFile que estará no projeto account-api, que será lido pelo Jenkins para realizar o Job.

Segue o JenkinsFile para explanação

<p> CODE: https://gist.github.com/vinirib/c8ddea074b1bda1f9b6fc81b15a50a53.js</p>

Observe o bloco post success, esse bloco é executado quando o stage Build and Publish Pacts é executado com sucesso, criando uma “cadeia” de Jobs no Jenkins, associando os jobs em um pipeline único. Neste caso, o próximo Job a ser executado é a execução do contrato pela parte do provider.


PACT-FLOW-PROVIDER-TEST-CONTRACT-client-api

Continuando sobre o sucesso do Job e Stage anteriores, este segundo passo fará o teste e validação do contrato do Provider a fim de validar que a integração esteja saudável. 

Para tal, ele segue o fluxo descrito no script Jenkinsfile a seguir:

<p> CODE: https://gist.github.com/vinirib/70e24018684e078e3adab8950718fc85.js</p>


Primeiro, realiza o download/sync do contrato Pact publicado no Pact Broker sob a tag 'CONTRACT-TEST' (Também usado no Stage inicial, ou seja, contrato Pact publicado pelo Consumer), este contrato Pact é utilizado pelo teste unitário do Provider que valida se as expectativas do Consumer estão sendo atendidas pelos seus resources end-points (API propriamente dita).

Nota importante que vale novamente lembrar: O comando das expectativas sempre partem do Consumer, cabendo ao Provider atendê-las.

Em caso de sucesso nesta validação, segue parapróximo Job e stages do Jenkins, como veremos logo a seguir.

Tão importante quanto o sucesso, neste cenário, é que havendo falhas, ou diga-se, quebra do Contrato Pact por parte do Provider, o CI será interrompido imediatamente deixando a experiência direta ao desenvolvedor. Visto que não será mais necessário esperar a execução estar em um ambiente integrado para perceber que os contratos de API não estão saudáveis.


PACT-FLOW-CONSUMER-CAN-I-DEPLOY

Este terceiro Job executa o stage descrito no script Jenkinsfile-can-i-deploy do Consumer o qual recebeu como sucesso do Pact Broker (stage anterior) à saúde da integração entre ele e o Provider.

<p> CODE: https://gist.github.com/vinirib/212ce7e28b338a0379d74e656d2702c7.js</p>

O que este Stage Jenkins realmente faz, é a validação segundo o Contrato Pact, dizendo se o Consumer está apto para deployment em ambiente produtivo. Para isso ele usa a ferramenta can-i-deploy ,  que foi descrita no Artigo 3 na sessão validando a integração com can-i-deploy.

Como descrito no artigo anterior, o Can-I-Deploy  retornará OK ou NOK (não OK) dizendo se o Consumer está apto para ser lançado em ambiente produtivo, baseando-se sempre na premissa de que o Contrato Pact está saudável entre Consumer e Provider


PROVIDER-CHANGED-CONTRACT-CHECK-INTEGRATION

Por fim, temos em nossa implementação de CI um Job e Stage que verificam uma possível mudança no Provider, e também valida se o Provider pode receber status de pronto para deployment em ambiente produtivo.

Este pipeline Job executa o stage do script  Jenkinsfile-can-i-deploy (neste momento para o Provider), Basicamente o que ele faz é testar o Provider contra o Contrato Pact firmado e esperado pelo Consumer e testar se o Provider está atendendo às expectativas firmadas no contrato. Ele não faz parte do flow de validação do contrato pelo consumer, mas, pensando no cenário em que o provider necessite validar apenas o contrato, pode-se rodar esse pipeline a fim de checar o estado do contrato no broker. Observem a diferença no script no nome do participant onde mencionamos o Provider para obter o último status de verificação de contrato.

<p> CODE: https://gist.github.com/vinirib/75b19e2b7e95e0ae0fdd47fe19d53f8f.js</p>


No cenário demonstrado acima, foi criado uma configuração de CI para integrar as tarefas de validação de integridade e garantia do design baseado em CDC (Consumer Driven Contract) completamente voltado para a experiência do desenvolvedor. Podemos afirmar também que o cenário acima se trata de um passeio pelo caminho feliz em que tanto Consumer quanto Provider estão com sua integração saudável. Embora o CI possa dar o feedback para o desenvolvedor ainda em tempo de desenvolvimento, a expectativa do fluxo acima acontece no caminho feliz. Mais adiante vamos explorar cenários mais caóticos e passíveis de acontecerem.

Para finalizar este tópico, deixamos um fluxo de estados para explanar como tudo acontece durante a integração do CI com o Pact e nossas implementações de Consumer e Provider



O segundo cenário 

Branch feature/provider-changed-contract

O segundo cenário, acontece quando o Provider faz alterações (sem aviso) em uma feature branch (endpoints-improvements), alterando seu resource endpoint o qual é demandado pelo Consumer

O Objetivo deste cenário é fazer com que, quando o IC for acionado, os Contratos Pact serão quebrados e o desenvolvedor do Provider saiba que está ferindo as expectativas impostas pelo Consumer.


Para sintetizar este cenário, basta observarmos o fluxo realizado nos passos 1 e 2 descritos na ilustração acima. Nele, após o Provider mudar sua branch de trabalho, de forma unilateral (o que é comum em implementações onde as Squads/Times são segregados), no instante do Build o desenvolvedor foi informado pelo Jenkins que sua implementação naquela branch (endpoints-improvement) quebram as expectativas do Consumer firmadas no Contrato de Pact.

Neste ponto, é importante relembrar que o Pact Broker é um componente passivo, ele espera que ambos os lados da integração (Consumer e Provider) atualizem-no e validem-se contra o Pact Broker. É neste ponto que entra esta integração via CI visa justamente deixar este processo atômico, ou seja, ativo ainda durante o tempo de desenvolvimento.

A ilustração abaixo demonstra o fluxo em modo linear:


O terceiro cenário 

Branch feature/consumer-make-some-changes

No terceiro cenário, o consumidor fez algumas melhorias e acionou o CI para verificar se há alguma alteração na integração, mas, para nossa surpresa, o provedor faz algumas alterações novamente sem orientação. O CI irá responder, de acordo com o PACT Broker, que as expectativas do Provider estão em desacordo com o Contrato Pact.



A ilustração abaixo demonstra o fluxo de modo linear:


Conclusão

Neste quarto artigo, aplicamos a abordagem de CDC (Consumer Driven Contract) de forma integrada em um ambiente de desenvolvimento, utilizando uma ferramenta de CI - Jenkins em nosso exemplo.

Pudemos também observar que, além da validação da saúde das integrações com base no Contrato Pact, não houve a necessidade de um ambiente de testes integrados e todo seu parque de dependências.

No decorrer deste artigo, passamos por três cenários comuns no desenvolvimento descentralizado de APIs, que demonstraram a abordagem do uso de PACT para formação de Contratos baseados nas expectativas do Consumer. Abrangemos desde o cenário em que tudo acontece de forma harmoniosa entre os times de Consumer e Provider, até quando há falta de sincronia e comunicação entre os times, o qual o CI junto ao Pact Broker, em conjunto. ajudam a detectar estas lacunas durante o ciclo de desenvolvimento.

Em resumo, tentamos demonstrar o ganho na experiência do desenvolvedor em se utilizar esta abordagem durante o processo de implementação de APIs. 

Nossa  proposta de solução CDC + CI trouxe melhor experiência para o desenvolvedor ainda em tempo de desenvolvimento, desacoplou parte da necessidade de testes integrados a todo momento, evitando também a esteira de qualidade sem antes validar que as integrações estarão saudáveis, no momento em que passar do desenvolvedor para equipe de qualidade e testes de software e assim por diante.


Observações

Se observarmos o design dos cenários, são perceptíveis diferentes branches concorrentes. Este artigo é uma ideia de CI para realizar automação no uso do framework PACT, mas a premissa de que sua equipe e outra equipe precisam combinar quem é o responsável por verificar a saúde das integrações antes da entrega na produção.

No arquivo de stage JenkinsFile, quando o Consumer e o Provider fazem a verificação, usa-se uma versão hash como forma de boa prática, no nosso caso, usamos git commit hash (no ambiente Jenkins) e usamos tags também para encontrar a última versão do código de forma simplificada. Essa prática pode ser encontrada na documentação do PACT.


Referências 





O que você achou deste conteúdo?
Quer receber nossos conteúdos?
Seu cadastro foi efetuado com sucesso! Enviaremos as novidades no seu email.
Oops! Something went wrong while submitting the form.