BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Implementando microservices com desenvolvimento orientado a comportamento

Implementando microservices com desenvolvimento orientado a comportamento

Pontos Principais

  • Behavior Driven Development para microservices concentra-se na colaboração da tríade - os desenvolvedores que são consumidores de API, desenvolvedores que são produtores de API e testadores;
  • Crie contratos bem definidos para interfaces de microsservices usando o design orientado à interface;
  • Os microservices normalmente exigem testes duplos para acelerar o teste de outros microservices;
  • Os testes devem ser independentes de implementação;
  • Criar testes para verificar se as falhas são tratadas adequadamente.

Microservices são invocados por outros microservices e por aplicativos inteiros. Essa dependência requer serviços bem definidos e bem testados. Essas metas podem ser alcançadas com comportamentos e contratos de interface especificados por testes. Com Behavior Driven Development (BDD), a funcionalidade de um serviço é descrita por testes que se concentram nas operações a serem executadas, e não na sintaxe dessas operações, como JSON ou XML. Automatizar esses testes normalmente requer testes duplos para outros microservices cujo comportamento é especificado por seus próprios testes de BDD. Interface Oriented Design (IOD) inclui outras obrigações contratuais de um microservice, como limitações no uso de recursos, taxa de transferência e relatório de erros. Juntos, BDD e IOD ajudam a descrever o comportamento de um serviço para que os consumidores possam entender e confiar nele facilmente.

Contexto

O BDD envolve a tríade - as três perspectivas do cliente, do desenvolvedor e do testador. Geralmente é aplicado ao comportamento externo de uma aplicação. Como os microservices são internos, a perspectiva do cliente é a do consumidor interno, ou seja, as partes da implementação (por exemplo, outros microservices) que usam o serviço. Portanto, a colaboração da tríade é entre os desenvolvedores que são consumidores de API, desenvolvedores que são produtores de API e os testadores.

O comportamento é frequentemente expresso num formulário Dado-Quando-Então, por exemplo. "Dado" um estado particular, "Quando" ocorre uma ação ou evento, "Então" o estado muda e/ou ocorre uma saída. O comportamento sem estado, como regras de negócios e cálculos, mostram apenas a transformação da entrada para a saída.

O design orientado por interface foca no princípio de padrões de design "Design para interfaces, não implementações". Uma entidade de consumidor deve ser escrita em relação à interface que um microservice produtor expõe, e não à sua implementação interna. Essas interfaces devem ser bem definidas, incluindo como elas respondem se não puderem executar suas responsabilidades. O Domain Driven Design (DDD) pode ajudar a definir os termos envolvidos no comportamento e na interface.

Os microservices podem ser síncronos, quando um consumidor chama outro microservice produtor diretamente e aguarda o resultado, ou assíncrono, onde o serviço responde a uma mensagem que o consumidor colocou em uma fila. Os exemplos deste artigo serão em serviços síncronos.

Contexto do exemplo

Um serviço fornece um conjunto coeso de operações relacionadas. Este exemplo em um aplicativo de pedidos é um serviço que calcula o desconto para um cliente que faz um pedido.

Um esboço do comportamento desse serviço poderia ser:

Get discount for a customer 
Given these inputs: 
     Customer category 
     Order Amount 
Then service outputs 
     Discount Amount 

O serviço pode calcular o resultado usando uma implementação no código, um banco de dados local ou contatando outros serviços. Vamos dar uma olhada nisso depois.

O serviço pode usar JSON ou XML como o protocolo de comunicação subjacente. No entanto, especificar o comportamento do serviço de maneira independente da implementação ajuda a separar as operações da sintaxe.

Qual é o comportamento?

Com o BDD, pode-se começar com dados de amostra para obter uma compreensão do comportamento desejado. A tríade pode sugerir:

Categoria do cliente Valor do Pedido Valor do desconto?
Bom 100.00 USD 1.00 USD
Excelente 100.00 USD 2.00 USD

As duas primeiras colunas são a entrada para o serviço e a coluna à direita é a saída do serviço.

A amostra identifica termos de domínio que podem precisar de descrições adicionais de seu comportamento, como valores permitidos. A tríade poderia concordar com os seguintes termos. O contrato implícito para o serviço é que ele deve retornar o valor adequado se as entradas estiverem dentro desses valores permitidos.

Categoria do cliente
Bom
Excelente
Super Excelentet
Moeda
USD
EUR
CAD

O comportamento, particularmente com microservices, geralmente inclui respostas que indicam falha na execução da operação. Definir as falhas potenciais ajuda o consumidor a determinar o que precisa manipular. Os consumidores podem usar bibliotecas padrão (por exemplo, Hystrix da Netflix) para lidar com algumas dessas falhas. Algumas falhas potenciais podem ser:

Falha
Sintaxe inválida
Tempo limite em serviços dependentes
Valor do parâmetro inválido

As falhas podem ser expressas como valores numéricos ou valores simbólicos no protocolo de comunicação. Usar nomes significativos no BDD ajuda a enfatizar a semântica da falha, em vez da sintaxe de falha. Por exemplo, se o valor transmitido como a categoria não aparecer na lista de valores válidos, o serviço retornará um indicador de falha que corresponde a "Parâmetro inválido". Isso pode ser representado no serviço subjacente por um retorno "400 - Bad Request" com uma carga útil de "Parâmetro inválido".

De maneira alternativa, pode-se definir um valor padrão para retornar (por exemplo, 0) se algum dos parâmetros for inválido. O serviço deve ter a responsabilidade de registrar esse problema, para que suas ramificações possam ser analisadas.

Os testes de serviço do BDD podem formar um contexto para os testes unitários de entidades (por exemplo, classes) que compõem o serviço. Por meio do processo de design, as responsabilidades pela passagem dos testes do BDD são atribuídas às classes e métodos. Os testes de unidade especificam essas responsabilidades.

Teste duplo

O consumidor de um serviço geralmente requer um teste duplo para os serviços que ele chama. Em particular, os serviços que são lentos, caros ou aleatórios precisam de testes duplicados. Se o comportamento do serviço de desconto nunca for alterado, o serviço de desconto de produção poderá ser usado no teste do consumidor. No entanto, as mudanças são inevitáveis, de modo que um teste duplo é normalmente necessário.

O teste duplo pode ser um que sempre retorna os mesmos valores, por exemplo.

Categoria do cliente Valor do Pedido Valor do desconto?
Bom 100.00 USD 1.00 USD
Excelente 100.00 USD 2.00 USD

Os testes do consumidor podem confiar nesses valores. Neste exemplo, o comportamento constante pode ser suficiente. No entanto, para outros testes, pode ser preferível que o teste configure a resposta do teste duplo.

O teste do consumidor configuraria o teste de desconto em dobro para responder com o valor fornecido quando a entrada é apresentada. Por exemplo:

Categoria do cliente Valor do Pedido Valor do desconto?
Bom 100.00 USD 1.00 USD

De maneira alternativa, o teste duplo de desconto poderia simplesmente responder com o valor, independentemente de qual entrada foi apresentada a ele.

Vamos ver como esse teste duplo pode se encaixar em um cenário maior. Este é o comportamento de um pedido que inclui um desconto e um imposto. O imposto é calculado por um microservice que é semelhante ao desconto.

Dado um cliente:

Categoria do cliente Localização
Bom North Carolina

E o desconto é configurado como:

Categoria do cliente Valor do Pedido Valor do desconto?
Bom 100.00 USD 1.00 USD

E o imposto é configurado como:

Localização Montante Taxa?
North Carolina 99.00USD 6.60 USD

Quando o cliente faz um pedido com:

Valor do Pedido
100.00 USD

Então os valores em ordem são:

Valor do Pedido Desconto Montante após desconto Taxa Valor Total
100 USD 1.00 USD 99.00 USD 6.60 USD 105.60 USD

Serviços Statefull

Se o serviço de desconto dependesse de um banco de dados local para obter informações sobre como calcular o desconto, o conteúdo do banco de dados representaria um estado do serviço. Como o estado do serviço é alterado em resposta a atualizações nos dados, deve ser documentado. Por exemplo, suponha que o serviço dependesse dos seguintes dados:

Categoria do cliente Nível limiar Porcentagem de desconto
Bom 100.00 USD 1%
Excelente 50.00 USD 2%

Haverá algum outro aspecto do serviço que permita a atualização desses dados. A atualização pode ser organizada de modo que os elementos individuais sejam atualizáveis ou que a tabela inteira seja atualizada de uma só vez. Veja um exemplo de teste comportamental para atualização individual.

Com os dados atuais:

Categoria do cliente Nível limiar Porcentagem de desconto
Bom 100.00 USD 1%
Excelente 50.00 USD 2%

Quando um elemento é atualizado:

Categoria do cliente Nível limiar Porcentagem de desconto
Excelente 50.00 USD 3.5%

Então os dados atualizados são:

Categoria do cliente Nível limiar Porcentagem de desconto
Bom 100.00 USD 1%
Excelente 50.00 USD 3.5%

Pode-se também verificar se os dados atualizados são usados para calcular o desconto:

Categoria do cliente Nível limiar Porcentagem de desconto
Excelente 100.00 USD 3.50 USD

O serviço de desconto pode ter um mecanismo de armazenamento persistente local para salvar os dados no exemplo anterior. No entanto, pode depender de outro serviço de persistência para manter esses dados. Se fosse esse o caso, os testes da seção anterior seriam aplicados a esse serviço. Toda dependência pode trazer outro problema. Qual deve ser o comportamento de um serviço se suas dependências estiverem indisponíveis? Para o serviço de desconto, deve indicar uma falha ou apenas retornar um valor padrão? Às vezes, uma política de falha global pode fornecer as respostas, mas muitas vezes a decisão depende do contexto do serviço.

Formulação de testes e automação

Uma vez que o comportamento de um microservice tenha sido acordado, ele pode ser formulado em testes automatizados. Existem várias estruturas de teste de microservice, como PACT ou Karate. Como alternativa, é possível usar estruturas do BDD, como o Cucumber ou o FIT. A implementação do teste, por exemplo as definições das etapas do Cucumber, usam bibliotecas de microservice para executar a solicitação/resposta. Informações ambientais adicionais podem ser fornecidas como parte do cenário ou como plano de fundo. Por exemplo, um arquivo de recurso do Cucumber pode incluir o seguinte. Pode haver diversas variações, dependendo das suas convenções de teste.

Scenario: Compute discount for an order amount 
Given setup is: 
| URL    | http://myrestservice.com  |
When discount computed with: 
| Method | GET               |
| Path   | discount          |
| Version| 1                 |
Then results for each instance are:
| Customer Category | Order Amount | Discount Amount? |
| Good              | 100.00 USD   | 1.00 USD         |
| Excellent         | 100.00 USD   | 2.00 USD         | 

Os valores nas duas primeiras colunas podem ser transferidos para qualquer que seja sua convenção de chamada, como em parâmetros de consulta. O resultado no corpo deve corresponder à terceira coluna. Se os nomes e valores da consulta forem os nomes e valores da coluna, isso diminuirá a impedância entre o teste e a implementação.

Para reutilização, as definições de etapa podem ser gravadas para qualquer serviço que compute um cálculo ou determine um resultado de regra de negócios para que uma biblioteca de análise comum possa ser usada. No exemplo anterior, uma convenção como o "?" (Como em Valor de desconto anterior) ajuda a distinguir para o analisador o que é entrada e o que é produzido.

Os testes também devem incluir testes para modos de falha, como:

Then results for each instance are:
| Customer Category | Order Amount | Discount Amount? | Result                  |
| Good              | 100.00 USD   | 1.00 USD         | OK                      |
| Not so Good       | 100.00 USD   | 2.00 USD         | Parameter value invalid |
| Excellent         | 100.00 ZZZ   | 2.00 USD         | Parameter value invalid |

Conclusão

A criação de microservices com BDD concentra-se na semântica das operações e não na sintaxe subjacente da implementação. Seguindo a diretriz do IOD de que os serviços são responsáveis por realizar sua operação e notificar alguém (consumidores ou um logger) de problemas, ajuda a delinear as responsabilidades pela reação a falhas. Ter serviços bem definidos torna muito mais fácil fazê-los trabalhar juntos para fornecer o comportamento externo desejado.

Sobre o autor

Ken Pugh ajuda as empresas a evoluir para organizações técnicas ágeis por meio de treinamento e coaching. Seus interesses especiais estão na criação de sistemas de alta qualidade com Desenvolvimento Orientado a Testes de Aceitação / Desenvolvimento Dirigido por Comportamento, acelerando o DevOps por colaboração e usando princípios enxutos para fornecer valor de negócios rapidamente. Escreveu vários livros de desenvolvimento de software, incluindo o vencedor do prêmio Jolt Award de 2006, Prefactoring, e seu mais recente: Lean-Agile Acceptance Test-Driven Development: Better Software Through Collaboration. Ken ajudou clientes de Londres a Boston, de Sydney a Pequim, a Hyderabad. Ele é o co-criador do curso SAFe® Agile Software Engineering. Você pode ver todos os seus serviços e contatá-lo em ken@kenpugh.com.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT