Estamos no nosso terceiro artigo sobre Consumer Driven Contract com PACT Framework. Nos dois artigos anteriores explicamos muita teoria sob o conceito do CDC, abordado um pouco de como o PACT implementa esse conceito.
Leia também:
– Testes de Contratos com Pact #1 – Conceitos
– Testes de Contratos com Pact, #2 – Consumer Driven Contract
Agora, trarei uma PoC com um pouco de Hands-on para sentirmos na prática como podemos validar nossas integrações com o PACT.
Bora?
O que vamos precisar?
- JDK Java 11 ou superior instalada
- IDEs (aqui uso Intellij e VsCode)
- Docker e Docker compose instalados
Integração REST
Desafio
Nosso desafio será desenvolver uma integração REST entre dois sistemas. De um lado temos a API de Clientes (CLIENT-API) que expõe dados do cliente, e do outro temos a API (ACCOUNT-API) que expõe dados da conta do cliente.
Como caso de uso, temos a necessidade de obter o saldo do cliente. Faremos enviando uma requisição para o sistema CLIENT-API, que realizará outra requisição para ACCOUNT-API, como parâmetro de requisição será passado o ID do cliente para obter o saldo.
Neste cenário temos os dois sistemas a CLIENT-API que é o nosso Consumer e a ACCOUNT-API que é o nosso Provider. Como assinatura dessa integração, temos uma comunicação via REST e comando GET (HTTP)
Veja abaixo uma imagem para ilustrar nossa integração.
Com essa integração acima, queremos incluir o PACT para validar se a integração desenvolvida pela CLIENT-API está correta e se sua comunicação com a ACCOUNT-API está saudável. Será este, basicamente, o fluxo que ocorrerá quando incluirmos o PACT em nossas APIs.
Todos os testes feitos aqui poderão ser encontrados no repositório do Github. Em ambos os projetos usamos Spring Boot + Maven + Swagger + OpenJDK 11.
Criando o teste via Consumer
Do lado do Consumer, precisamos criar um teste similar à testes unitários BDD (Behavior Driven Development) no qual faremos nossas asserções de como consumimos nossa integração. O primeiro passo é adicionar as dependências do pact no nosso arquivo Maven.
<p> CODE: https://gist.github.com/vinirib/34f16f36e60b603c07959b60174e8b43.js</p>
Acima incluímos o plugin do maven e algumas informações de localização do broker e do diretório onde os contratos estarão em nosso projeto.
- pactBrokerUrl – Url do broker Pact (estará em um container docker)
- pactDirectory – Diretório do projeto onde estarão nossos contratos json gerados pelo plugin do Pact
- trimSnapshot – remover o nome snapshot quando enviar nosso arquivo para o broker
Agora criaremos nosso teste unitário com a lib do Pact.
Na sequência do código serão explicados detalhes relevantes.
<p> CODE: https://gist.github.com/vinirib/c57e4dc83d348b068cfd6176286c4770.js</p>
Alguns detalhes relevantes do código:
- – Utilizei a classe de extensão do Pact PactConsumerTestExt para o Spring saber que os testes rodarão no contexto do Pact.
- – Método onde serão escritos as expectativas do contrato, aqui você dará um nome ao Provider (o provider terá que chamá-lo pelo mesmo nome) e o nome do seu consumer. Esse nome deverá ser diferente caso tenham mais consumers para o mesmo provider.
- – A classe PactDslJsonBody é responsável por agrupar a resposta que iremos receber no contrato, nesta classe foi construído o Objeto de Resposta.
- – No Builder foram montadas as expectativas, no given escrevemos o nome do cenário que estamos construindo, esse nome deverá ser o mesmo que será mencionado no Provider. Os outros métodos são auto descritivos, definimos os Headers, a resposta que esperamos receber e o objeto que receberemos, neste caso referenciamos o PactDslJsonBody.
- – Aqui escrevemos um Junit das nossas expectativas, quando esse método rodar em nossos testes, ele terá subido um contexto, um endpoint do jeito que definimos nas expectativas e fará a requisição conforme escrevemos, escrevi asserções para garantir que a chamada está de acordo com o que eu espero.
Agora que escrevemos nossos testes, executaremos os testes como um JUnit convencional.
Confira o resultado:
Após rodarmos os testes com sucesso, será gerado o arquivo de Contrato na pasta ./target/pacts.
Veja o exemplo de um Contrato Pact:
<p> CODE: https://gist.github.com/vinirib/de4ca7f2865183fc36df725e0586c478.js</p>
Esse arquivo será enviado para o Pact Broker para que o Provider obtenha o Contrato Pact e valide contra sua implementação se ele cobre as expectativas do Consumer, ou seja, se nossa integração está saudável.
Enviando o contrato para o PACT Broker
Agora que temos nosso contrato gerado pelo PACT, precisamos enviá-lo para o Pact Broker.
Foi criado um Docker Compose para subir nosso Broker Pact.
<p> CODE: https://gist.github.com/vinirib/860aec43ee60af0451af3b3d0fd1dc0b.js</p>
Ao subir os containers do compose, teremos o Pact Broker rodando na URL: Http://localhost
A seguir, vamos enviar o contrato da CLIENT-API para o Pact Broker via comando maven.
Para isso, vamos abrir uma janela de terminal na raiz do projeto e executamos o seguinte comando:
<p> CODE: https://gist.github.com/vinirib/a848c84eaaefc52c7595900fe351bff8.js</p>
Após o comando maven ser executado e completado com sucesso, podemos acessar o Pact Broker no navegador e verificamos que o Contrato Pact foi publicado.
Ao clicar nos links do Consumer ou do Provider, podemos ver que o PACT cria uma espécie de gráfico demonstrando como estão nossas integrações.
Neste ponto, a responsabilidade do Provider será de construir uma classe Java que chamará o contrato que publicamos para validar a integração, ele também publicará no Pact Broker o resultado do seu teste.
Validação do contrato pelo Provider
Nosso próximo passo é validar o Contrato Pact no Provider. Para isso desenvolvemos um código em formato de teste unitário que ficará no Provider e baixará o arquivo de Contrato Pact vindo do Pact Broker e executará os testes de asserção (valide se sua implementação de API está de acordo com o Contrato definido pelo Consumer)
Para isso, primeiro colocaremos as dependências no projeto maven do Provider.
<p> CODE: https://gist.github.com/vinirib/a225f8e5d92b89a80a921e6bbd589fd2.js</p>
A classe de testes:
<p> CODE: https://gist.github.com/vinirib/5e66563799833638dd21c289c073f144.js</p>
Vamos analisar os detalhes do código acima, já que aqui é um pouco diferente do consumer:
- – Na Annotation @Provider, mencionamos o nome dado ao Provider no Contrato. Em @PactBroker incluímos o endereço do nosso Pact Broker (Que executamos via Docker no tópico anterior) para que o contrato seja baixado para o Provider. Na Annotation @SpringBootTests incluímos uma random port (porta randômica/aleatória) para que o Spring suba o contexto da aplicação em uma porta aleatória pois não importará essa porta, ela não será consumida por nenhuma aplicação.
- – @LocalserverPort é uma Annotation que irá gerar a porta aleatória que será usada quando subir o contexto da aplicação quando executarem os testes.
- – Neste ponto são ajustados os detalhes de contexto do servidor web, que será executado quando o teste rodar.
- – Indica que ao executar os testes queremos que os resultados sejam publicados no Pact Broker.
- – Bloco de código que fará a validação do contexto, entre eles, o Pact Broker, URLs de conexão, etc.
- – Em @State, chamaremos nosso cenário de teste de Contrato Pact, o mesmo nome utilizado pelo Consumer. Todos esses dados podem ser encontrados no Pact Broker, pela interface web em http://Iocalh0st, pois o contrato já está publicado lá – Pelo Consumer.
- – Na Controller do Provider é injetado o AccountService, neste caso eu quero mockar este @Service, isso facilita caso tivéssemos um acesso ao banco de dados ao realizar requisições na Controller. Aqui está sendo usado o Mockito para simular o Service.
Ao executar os testes do Provider, recebemos o resultado na console:
Quando a execução é finalizada, como demonstrado na imagem acima, no mesmo momento o resultado foi publicado e estará visivel no Pact Broker:
No Pact Matrix, também podemos constatar as versões que foram testadas de nosso Contrato Pact, basta clicar no ícone que parece uma planilha.
Verifique que tudo no PACT Broker é versionado, neste momento da PoC, não está sendo usado todos os recursos de versionamento disponíveis no PACT Broker, mas saiba que eles são úteis para cenários que temos diversas alterações ou sugestões de mudança na integração. Por exemplo, podemos validar em versões apartadas antes de publicar algo em um ambiente produtivo.
Por fim, validando a integração com can-i-deploy
Agora que fizemos o fluxo do PACT tanto no Consumer quanto no Provider, surge a questão: como usamos essas informações para validar uma integração?
- Quero saber pelo lado do meu consumer se meu código de contrato está correto.
- Pelo Provider, quero saber se meus consumers estão integrando de forma correta, para que eu entregue a integração em ambiente produtivo de forma funcional
- Como fazer realizar tais validações de “Estou pronto para Deploy”?
Existem duas maneiras. A primeira, é que cada um acesse diretamente no Pact Broker e de forma manual validem se seu código está correto, sendo que para você faça isso ou você irá checar se a outra equipe rodou os testes ou você deverá ser avisado por eles que o teste foi feito, de forma totalmente manual.
A segunda maneira depende que os dois lados tenham rodado seus testes e é usando uma ferramenta do PACT chamada can-i-deploy, ela serve exclusivamente para este propósito, é um CLI que passamos a integração que será validada e ela retorna um OK ou NOK (Não OK).
Segue um exemplo de uso via linha de comando:
<p> CODE: https://gist.github.com/vinirib/5b7adeb49067de951680f8de92628c58.js</p>
O resultado:
<p> CODE: https://gist.github.com/vinirib/b360790eb15f0e5507af09d47de01aba.js</p>
Com o resultado do can-i-deploy em mãos, passamos a saber que nossa integração está criada corretamente e que o PACT nos ajudou a validar isso de uma forma eficiente com a manipulação e governança dos Contratos. Lembrando que nenhum momento o ambiente de integração foi necessário para realizar essa validação, a única necessidade no meio do caminho foi o Pact Broker que é a ferramenta que faz a consolidação dos Contratos Pact firmados.
Conclusão
Acabamos de usar o PACT para validar nossa integração e fizemos isso de forma manual na geração do contrato pelo maven no Consumer, validando o contrato pelo Provider e na verificação da integração com o can-i-deploy.
Podemos constatar a eficiência de um fluxo Consumer Driven Contract e não necessitamos de ambiente integrado.
No próximo artigo, vou te apresentar como utilizar o PACT junto com ferramentas de CI para automatizar todas as tarefas manuais que fizemos neste artigo. Também será demonstrado como a prática é útil, caso algum lado mude o contrato, além de como incluir cenários em que quebraremos o Contrato nos testes.
Pronto para mais CDC? Não deixe de comentar o que achou! Essa troca é muito importante para a nossa comunidade!
Algumas referências:
– https://github.com/vinirib/zup-pact-workshop
– https://medium.com/kreuzwerker-gmbh/introduction-to-consumer-driven-contract-testing-3a130c8c2ea0