As 6 melhores práticas para arquiteturas baseadas em microservices

A ideia deste artigo é servir como um guia de boas práticas para construção de uma arquitetura de microservices realmente profissional, seja lá qual tecnologia estiver utilizando. Apesar disso, serão dadas dicas adicionais relacionadas especificamente à plataforma Node.js que lhe serão muito úteis caso use essa tecnologia.

Caso você nunca tenha desenvolvido microservices em Node.js antes, esta série de artigos lhe dará uma boa base, bem como o meu curso onde ensino essa arquitetura em vídeo aulas.

As dicas são:

  1. Tente alcançar a Glória do REST
  2. Use um serviço de configuração
  3. Geração automática do código cliente
  4. Continuous Delivery
  5. Monitoramento e Logging
  6. API Gateway

Vamos lá!

Curso Node.js e MongoDB
Curso Node.js e MongoDB

#1 – Tente alcançar a Glória do REST

A imagem abaixo ilustra o modelo de maturidade do Leonard Richardson, um famoso desenvolvedor de APIs que programa desde os 8 anos de idade e tem vários livros publicados.

Modelo de Maturidade REST
Modelo de Maturidade REST

Neste modelo o objetivo é ir do Pântano do POX (Plain-Old XML) até a Glória do REST que basicamente é ter uma web API RESTful. Quanto mais RESTful for sua web API, mais próxima da Glória ela estará e consequentemente seus microservices estarão melhor desenvolvidos.

Martin Fowler resume este modelo da seguinte forma:

  • Nível 0 (POX): aqui usamos HTTP para fazer a comunicação e só. Não aproveitamos nada da natureza da web exceto seu túnel de comunicação, sendo que o envio e recebimento de mensagens baseia-se em XML-RPC ou SOAP (ambos POX).
  • Nível 1 (Recursos): aqui evoluímos a API através da segregação dos recursos em diferentes endpoints, ao contrário do nível 0 onde tínhamos apenas um endpoint com tudo dentro, respondendo de maneira diferente conforme os parâmetros no XML de envio.
  • Nível 2 (Verbos): aqui finalmente passamos a usar o protocolo HTTP como ele foi projetado, fazendo com que o verbo utilizado na requisição defina o comportamento esperado do recurso. Geralmente 99% das web APIs do mercado param por aqui, como a maioria dos tutoriais de web APIs aqui do blog aliás.
  • Nível 3 (Hipermídia): este é o último obstáculo a ser alcançado para alcançar a Glória do REST e envolve a sigla HATEOAS que significa Hypertext As The Engine Of Application State. Aqui, o retorno de uma requisição HTTP traz, além do recurso, controles hipermídia relacionados ao mesmo, permitindo que o requisitante saiba o que fazer em seguida, ajudando principalmente os desenvolvedores clientes.

Embora este modelo não seja uma verdade universal, é um excelente ponto de partida para deixar seus microservices RESTful. E se você usa Java, sugiro usar o Spring HATEOAS para cuidar do último quesito de Hypermedia Controls.

#2 – Use um serviço de configuração

Aqui a dica é usar o Consul, tanto no Java quanto no Node, para lidar com dezenas de microservices, cada um com as suas configurações personalizadas e com as inúmeras variações para cada ambiente (dev, homolog, prod, etc).

O Consul não serve apenas como um repositório de chave-valor para gerenciar configurações de maneira distribuída e real-time (sim, você nunca mais vai ter de fazerd eploy apenas pra mudar um config), mas também para segmentação de serviços (garantindo comunicação segura entre os serviços) e service discovery, um dos maiores desafios de arquiteturas distribuídas em containers.

#3 – Geração automática de código cliente

O código cliente para consumir microservices é sempre algo pouco criativo que faz sempre as mesmas coisas usando as mesmas bibliotecas líderes de mercado, certo?

Sendo assim, a recomendação é usar algum gerador de código client-side como o do Swagger, que inclusive suporta múltiplas linguagens de destino.

#4 – Continuous Delivery

Quando você constrói uma arquitetura com dezenas de micro serviços e quer agilidade no deploy só há uma alternativa recomendada hoje em dia: containerização de serviços.

Usar Docker com Jenkins é a dobradinha mais usada atualmente para ganhar eficiência no pipeline de deploy. Realmente não há como fugir de ao menos conhecer essa dupla e não tem como falar de microservices sem falar de CD.

Pretendo fazer tutoriais no futuro de uso de Jenkins e Docker, uma vez que cada mais estas skills de DevOps tem sido necessários para se manter competitivo no mercado de trabalho hoje em dia, pelo menos em posições de liderança técnica. Sendo assim, aguarde um post futuro sobre Continuous Delivery com Jenkins e Docker.

#5 – Monitoramento e Logging

Quando você tem apenas uma web API para cuidar, é muito simples usar os logs do seu servidor web para saber o que aconteceu com sua aplicação e monitoramentos simples como o PerfMon do Windows ou até um top tosco no console Unix. Monitoramentos web como StatusCake e Pingdom também são bem populares, pingando em endpoints de health-check.

Mas e quando você tem uma ou mais dezenas de microservices? Como gerenciar o monitoramento e o logging de cada um deles, principalmente considerando ambientes dinâmicas como nuvens containerizadas com auto scaling?

A questão é: independente do ‘como’, você terá de dar um jeito de ter uma solução profissional de monitoramento e logging, isso é vital para garantir a disponibilidade da sua arquitetura e responder rapidamente a incidentes. Uma dica importante é ter um bom APM, como o Zipkin, que é uma boa opção open-source.

#6 – API Gateway

Já falei muito disso no artigo em que ensino como criar uma solução em Node.js para API Gateway. Soluções mais profissionais e corporativas como WSO2 e Zuul são mais indicadas nos casos de arquiteturas muito robustas, embora o Netflix tenha criado a sua própria solução em Node.js.

A questão é que você tem de ter uma camada antes dos seus microservices para acesso externo. Não apenas por uma questão de segurança mas para uma questão de agregação dos dados dos diferentes mcroservices que compõem a resposta de uma requisição, garantindo maior performance e usabilidade no lado do cliente.

Estas foram 6 dicas de como tornar sua arquitetura de microserviços mais profissional.

Até a próxima!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!

Agile Testing: Dicas para testes de software em times ágeis

Então a empresa decide adotar métodos ágeis top-down. Show, geralmente o alto escalão é quem boicota esse tipo de iniciativa. Começar com seu apoio já é meio caminho andado. A outra metade do caminho? Convencer o restante da empresa a fazer o movimento bottom-up da transformação ágil.

Embora para algumas pessoas isso possa soar muito fácil, não é tão fácil quanto parece, principalmente quando colocamos dois elementos-chave na equação de qualquer transformação ágil: sustentação e testes. Pretendo falar de sustentação em outro artigo, hoje vou me focar na questão de $1M que é: como testar de maneira ágil?

O papel do testador em um Time Ágil (QA)

O primeiro ponto a ser definido, antes de falar de práticas é do papel do testador. Primeiro que em Times Scrum (e 90% dos times usam Scrum ou ao menos ScrumBut) não existe o papel do analista de testes. Todo mundo que não é PO nem Scrum Master é um Desenvolvedor. Um analista de testes é alguém que colabora no desenvolvimento do produto, logo, é co-responsável por todo o ciclo de desenvolvimento do mesmo, não apenas os testes.

Uma vez que, ao contrário do modelo Waterfall, não teremos uma fase de testes ao final do projeto, cabe ao profissional de testes participar ativamente de todo o processo, desde o planejamento (quer alguém melhor do que um QA para ajudar com Critérios de Aceite?), ao desenvolvimento (seja Pair Programming ou mesmo desenvolvendo testes automatizados) e finalmente os testes propriamente ditos, que, ao invés de serem realizados todos no final do projeto (ou mesmo no final da iteração) devem ser realizados conforme os itens do Sprint Backlog venham sendo entregues pelos desenvolvedores.

Ufa! Sim, um QA Ágil tem muito mais trabalho que um QA tradicional, tendo de acompanhar o restante do time durante o dia a dia do projeto, ajudando-os, ao invés de focar-se em encontrar o maior número possível de bugs no final do projeto.

Não obstante, vale lembrar que qualidade não é responsabilidade exclusiva do QA, mas sim de todo o time, e cabe à ele também ressaltar e criar uma cultura de qualidade dentro do time constantemente, como se fosse um Scrum Master da qualidade ou QA Master. 😀

Teste Ágil x Tradicional
Teste Ágil x Tradicional

Teste Tradicional x Ágil

Essa mudança de mindset do profissional de qualidade é vital para que ele consiga entrar no modelo ágil do time. Isso porque, não raro, times de desenvolvedores adotam métodos ágeis mas não agilizam os testes, geralmente delegando esta atividade para um time de QA, que muitas vezes fica em outro setor.

Neste cenário bi-modal, geralmente o time de QA vai se focar em construir pesadas documentações com casos de teste e registros dos testes realizados, além de exigir do time de desenvolvimento as demais documentações do projeto, como casos de uso (às vezes aceitam histórias de usário). O que acontece é que dessa forma gasta-se um tempo e esforço enormes produzindo documentação quando poderíamos aproximar profissionais de QA dentro dos Times Scrum e colocá-los a trabalhar com o time, e não para o time.

Essa burocracia, aliada a uma cultura de “última linha de defesa da qualidade do software” gera um histórico enorme de conflitos entre devs e QAs que é totalmente desnecessário. No momento que algumas empresas separam a responsabilidade da qualidade entre times diferentes, um inadvertidamente culpa o outro por todo o tipo de problema encontrado no processo de desenvolvimento. Frases como:

  • Os devs me largam software de qualquer jeito pra testar!
  • Os QAs estão atrasando a entrega do projeto!
  • Sou o maior, achei 30 bugs na última release do software, esses devs são uns incompetentes!
  • Eu não vou testar o software que desenvolvi, os QAs são pagos pra isso!

Entre outras pérolas.

O fato é que não há como ter um time ágil se o processo de QA não é ágil. A sprint não pode ser considerada bem sucedida se não temos todos itens do sprint backlog como DONE. E francamente, eles não podem estar definidos como DONE se não tiverem sido testados ainda.

O mindset do QA Ágil

Ao invés de se posicionarem como a última linha de defesa, os testadores de um time ágil devem se colocar como os evangelistas da qualidade dentro do time:

  • ajudando o PO a escrever Critérios de Aceite para suas User Stories;
  • realizando ATDD, BDD e/ou Specification by Example;
  • ajudando os devs a escreverem testes unitários automatizados;
  • desenvolvendo testes funcionais automatizados;
  • ajudando o Scrum Master com as métricas de qualidade do time;
  • e, em último caso, realizando testes funcionais tradicionais.

Ou seja, o trabalho do QA não pode estar limitado a escrever documentos de teste no início da sprint e testar o software no final (isso sempre dá errado), mas sim desenvolver especificações e testes no início da sprint e iniciar os testes de software conforme os itens de backlog forem sendo entregues.

Só desta forma é que é possível garantir qualidade e agilidade ao mesmo tempo. Para resultados excepcionais, os profissionais do time devem estar dispostos a uma dedicação excepcional.

Automação de Testes

Você deve ter percebido que, em vários momentos, eu falei de automação de testes (ou alguma de suas práticas). Isso porque simplesmente não tem como falar de teste ágil de software sem falar de automação de teste.

Imagine o seguinte cenário: um desenvolvedor termina de codar a US001, ele entrega para o QA testar e o QA testa a US001. Outro desenvolvedor termina de codar a US002 e entrega para o QA. Como ela possui dependência com a US001, o QA testa a US002 e a US001 para garantir que tudo continua funcionando. O primeiro desenvolvedor termina uma US003 que causou mudanças em trechos de código que impactam outra user stories do sistema, mas sem clareza de quais são, logo, o QA faz uma regressão, testando US001, US002 e US003.

E quando o sistema atingir a marca da US100? Ou da US1000? Quantos QAs você vai precisar no seu time para atender à demanda de testes necessária?

Automatizar testes é a única maneira de garantir uma cobertura de qualidade no seu software. Não é uma questão de ser ágil ou não, mas uma questão de ser honesto quando você diz que seu sistema está 100% testado ou não.

Escrever testes unitários é o primeiro passo e deve ser regra dentro do seu time de desenvolvimento independente de usarem TDD ou não. Mas são nos testes funcionais automatizados que está a beleza de frameworks como ATDD, BDD e Specification by Example. Profissionais de qualidade modernos e realmente ágeis não escrevem documentação de teste para depois passarem horas fazendo testes manuais. Eles escrevem requisitos junto ao profissional de negócio de maneira que a especificação é o próprio teste funcional automatizado, ao mesmo tempo que garantem que os devs vão lhe entregar incrementos pequenos e facilmente testáveis, além do que eles já devem ter passado por uma bateria de unit tests.

Se você é um profissional de testes e quer continuar com demanda no mercado, principalmente nas empresas mais top de tecnologia, você tem de aprender automação de testes. Fazer teste funcional hoje, é o mesmo que querer programar perfurando cartões: lento, ineficiente e não escala bem.

Referências

Vou ser sincero com você: meu livro de Scrum e Métodos Ágeis (no final deste post) não fala de teste ágil de software. Mas as leituras abaixo falam muito bem:

Tem outros livros ou dicas para compartilhar? Deixe nos comentários!

Quer saber mais de Scrum e Métodos Ágeis? Conheça o meu livro sobre o assunto!

Autenticação JSON Web Token (JWT) em Node.js

Faz algum tempo que ensino aqui no blog a como fazer APIs em Node.js, uma vez que este é o cenário mais comum de uso com a plataforma.

Já ensinei a fazer APIs com MongoDB, MySQL e SQL Server. Já ensinei a como estruturar suas APIs em formato de micro serviços e como criar um API Gateway.

No entanto, pouco falei aqui sobre segurança em APIs. O mais próximo disso é o tutorial de Passport e as dicas que dei de usar o pacote Helmet em alguns posts.

Hoje vamos falar de JSON Web Tokens como uma forma de garantir a autenticação e autorização de uso de APIs de maneira bem simples e segura, sendo o JWT um padrão para segurança de APIs RESTful atualmente.

JSON Web Tokens

JWT, resumidamente, é uma string de caracteres codificados que, caso cliente e servidor estejam sob HTTPS, permite que somente o servidor que conhece o ‘segredo’ possa ler o conteúdo do token e assim confirmar a autenticidade do cliente.

Ou seja, quando um usuário se autentica no sistema (com usuário e senha), o servidor gera um token com data de expiração pra ele. Durante as requisições seguintes do cliente, o JWT é enviado no cabeçalho da requisição e, caso esteja válido, a API irá permitir acesso aos recursos solicitados, sem a necessidade de se autenticar novamente.

O diagrama abaixo mostra este fluxo, passo-a-passo:

Fluxo JWT
Fluxo JWT

O conteúdo do JWT é um payload JSON que pode conter a informação que você desejar, que lhe permita mais tarde conceder autorização a determinados recursos para determinados usuários. Minimamente ele terá o ID do usuário autenticado, mas pode conter muito mais do que isso.

Estruturando a API

Antes de começarmos esta API Node.js usando JWT vale ressaltar que o foco aqui é mostrar o funcionamento do JWT e não o funcionamento de uma API real.

Não focaremos no processo de autenticação inicial, que pode tranquilamente ser feito usando Passport ou diretamente com user/password batendo no servidor. Vamos mockar os dados de autenticação inicial para ir logo para a geração e posterior verificação dos tokens.

Para não termos de começar a configurar uma API do zero, recomendo baixar os fontes do tutorial de API Gateway, uma vez que o API Gateway é uma API bem simples, com apenas um arquivo index.js. Caso não queira ter de subir as APIs fake às quais ele depende, basta substituir o código de proxy por um retorno JSON fake que não vai fazer diferença nenhuma para este tutorial.

Você deve ter o seguinte index.js do API Gateway em mãos:

Com esse JS em mãos, vamos instalar algumas novas dependências na nossa aplicação que nos permitirão o uso de JWT:

A saber:

  • jsonwebtoken: pacote que implementa o protocolo JSON Web Token;
  • dotenv-safe: pacote para gerenciar facilmente variáveis de ambiente;

Vamos começar com o dotenv-safe, criando dois arquivos ocultos. Primeiro, o arquivo .env.example, com o template de variáveis de ambiente que vamos precisar:

E depois, o arquivo .env, com o valor do segredo à sua escolha:

Este segredo será utilizado pela biblioteca jsonwebtoken para criptografar o token de modo que somente o servidor consiga lê-lo, então é de bom tom que seja um segredo forte.

Para que esse arquivo de variáveis de ambiente seja carregado assim que a aplicação iniciar, adicione a seguinte linha logo no início do arquivo index.js da sua API, aproveitando para inserir também as linhas dos novos pacotes que vamos trabalhar:

Isso deixa nossa API minimamente preparada para de fato lidar com a autenticação e autorização.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

Autenticação e Autorização

Caso você não saiba a diferença, autenticação é você provar que você é você mesmo. Já autorização é você provar que possui permissão para fazer ou ver o que você está tentando.

Antes de emitir o JWT é necessário que o usuário passe por uma autenticação tradicional, geralmente com usuário e senha. Essa informação fornecida é validada junto a uma base de dados e somente caso ela esteja ok é que geramos o JWT para ele.

Assim, vamos criar uma nova rota /login que vai receber um usuário e senha hipotético e, caso esteja ok, retornará um JWT para o cliente:

Aqui temos o seguinte cenário: o cliente posta na URL /login um user e um pwd, que simulo uma ida ao banco meramente verificando se user é igual a luiz e se pwd é igual a 123. Estando ok, o banco me retornaria o ID deste usuário, qie simulei com uma constante.

Esse ID está sendo usado como payload do JWT que está sendo assinado, mas poderia ter mais informações conforme a sua necessidade. Além do payload, é passado o SECRET, que está armazenado em uma variável de ambiente como mandam as boas práticas de segurança. Por fim, adicionei uma expiração de 5 minutos para este token, o que quer dizer que o usuário autenticado poderá fazer suas requisições por 5 minutos antes do sistema pedir que ele se autentique novamente.

Caso o user e pwd não coincidam, será devolvido um erro ao usuário.

Vamos aproveitar o embalo e vamos criar uma rota para o logout:

Aqui apenas anulamos o token, embora esta rota de logout seja completamente opcional uma vez que no próprio client-side é possível destruir o cookie de autenticação e com isso o usuário está automaticamente deslogado.

Mas será que está funcionando?

Aí que entra a autorização!

Vamos criar uma função de verificação em nosso index.js, com o intuito de, dada uma requisição que está chegando, a gente verifica se ela possui um JWT válido:

Aqui eu obtive o token a partir do cabeçalho x-access-token, que se não existir já gera um erro logo de primeira.

Caso exista, verificamos a autenticidade desse token usando a função verify, usando a variável de ambiente com o SECRET. Caso ele não consiga decodificar o token, irá gerar um erro. Em seguida chamamos a função next que passa para o próximo estágio de execução das funções no pipeline do middleware do Express, mas não antes de salvar a informação do id do usuário para a requisição, visando poder ser utilizado pelo próximo estágio.

Ok, entendi que esta função atuará como um middleware, mas como usaremos a mesma?

Basta inserirmos sua referência nas chamadas GET que já existiam em nosso API Gateway:

Assim, antes de redirecionar os GETs para as APIs de destino, o API Gateway vai criar essa camada intermediária de autorização baseada em JWT, que obviamente vai bloquear requisições que não estejam autenticada e autorizadas, conforme as suas regras para tal.

O resultado, tentando chamar a rota /users sem estar autenticado é esse:

JWT Inexistente
JWT Inexistente

Agora, se realizarmos a autenticação usando POSTMAN:

Login via POSTMAN
Login via POSTMAN

Conseguiremos fazer novas chamadas aos endpoints da API, pois agora temos o token JWT para passar no header da nossa requisição (x-access-token):

Requisição realizada com sucesso
Requisição realizada com sucesso

E por fim, como este token está programado para expirar 5 minutos após a sua criação, as requisições podem ser feitas usando o mesmo durante este tempo, sem novo login. Mas assim que ele expirar, receberemos como retorno…

Token expirado
Token expirado

Bacana, não?!

E com isso encerro o artigo de hoje. Note que foquei no uso do JWT, sem entrar em muitos detalhes de como você pode estruturar sua autenticação inicial (login e senha) e nem como você pode estruturar o seu modelo de autorização (terá perfis de acesso, por exemplo?).

Quem sabe não abordo este tema de maneira mais avançada e completa em outra oportunidade?

Um abraço e até mais!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!