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!

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!

API Gateway em Arquitetura de Microservices com Node.js

Recentemente escrevi uma série de artigos sobre arquitetura de microservices usando Node.js e MongoDB. Na situação atual desta série temos alguns microservices criados e, com isso, uma complexidade que não existia nos webservices monolíticos de antigamente: múltiplos endpoints de múltiplos serviços para uma única aplicação.

Será que o(s) client(s) deveriam conhecer as especificidades de cada microservice? E no caso de algoritmos que são padrões a todos os microservices (como autenticação e autorização), devem ser repetidas em todos microservices?

Como resolver estas e outras questões sem gambiarra, de maneira corporativa? Usando um API Gateway, conforme já comentei no artigo sobre boas práticas com microservices.

Você verá neste artigo:

  1. API Gateway e API Manager
  2. O que vamos fazer
  3. Criando a API gateway
  4. Testando e indo além!

Se quiser ver este mesmo tutorial em formato de vídeo aula, confira o meu Curso de Node.js e MongoDB!

#1 – API Gateway e API Manager

Resumidamente um API Gateway fornece um ponto de acesso único à sua arquitetura de microservices. Não importa quantos microservices você tenha, colocando um API Gateway à frente deles você terá uma única URL para se preocupar. O API Gateway por sua vez roteia e gerencia o tráfego de requisições para os microservices de destino.

Já um API Manager é um API Gateway mais ‘bombado’, que além de atuar como proxy realiza toda uma governança das chamadas realizadas aos microservices, como analytics, versionamento de APIs, caching, dashboards, segurança, transformação de dados, agregação de dados, etc.

Uma abordagem possível é ter um API Gateway por diferente client, como na imagem abaixo:

API Gateway por client
API Gateway por client

Uma solução bem comum no mercado é usar um proxy reverso como nginx, mas Node.js também é um excelente opção para construção de API Gateways quando se deseja um controle maior das requisições, permitindo fazer algo mais próximo de um API Manager sob medida. É exatamente o que fez a Netflix, que construiu um API Gateway em Node.js para ficar na frente das suas centenas de APIs Java, como mostra o diagrama abaixo:

Arquitetura Netflix
Arquitetura Netflix

Especialmente para clientes mobile, que possuem severas limitações de latência e qualidade de conexão à Internet, um API Gateway pode simplificar em uma única chamada mobile, diversas requisições aos microservices que, acontecendo na rede local do datacenter, serão muito mais velozes do que se o dispositivo móvel tivesse de fazer diversas chamadas.

A diferença entre API Gateway e API Manager é tênue, uma vez que há diversos fabricantes e nomenclaturas diferentes no mercado. O termo mais comumente utilizado é de API Gateway, geralmente sendo chamados de API Managers as soluções comerciais corporativas.

#2 – O que vamos fazer

A ideia deste tutorial é fazer um API Gateway simples em Node.js, seguindo a arquitetura abaixo:

API Gateway Simples
API Gateway Simples

Nesta arquitetura, o API Gateway recebe e roteia as requisições para microservices dentro de uma DMZ (Zona Desmilitarizada ou Zona de Perímetro), um conceito de segurança da informação para proteger a rede da empresa dos serviços e vice-versa.

Assim, nosso cliente somente conhecerá uma API, que é a API Gateway, podendo existir muitos outros microservices por trás dele. Essa abordagem é especialmente interessante quando estamos refatorando serviços monolíticos para microservices: podemos substituir as chamadas ao serviço antigo para chamadas ao API Gateway e roteamos para o serviço antigo. Conforme o time for ‘escamando’ o serviço antigo em microservices, vamos ajustando no API Gateway para chamar ora o serviço antigo, ora os novos microservices. O diagrama abaixo mostra isso:

API Gateway e Design Evolucionário
API Gateway e Design Evolucionário

Mais pra frente podemos aproveitar esta centralização das requisições para adicionar segurança à todas as chamadas como autenticação e rate limit (controle de uso das APIs, para evitar exageros).

Para executar este tutorial na prática é importante que você tenha alguns microservices prontos na sua máquina, no mínimo dois. Se você não tiver, nos fontes deste tutorial eu inclui dois micro serviços fake que sempre retornam os mesmos dados mockados, para que você possa testar. No entanto, o mais recomendado é que você realize os tutoriais de microservices que estão aqui no blog para realmente aprender o tópico completo.

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

#3 – Criando a API Gateway

 

Por incrível que pareça, criar o esqueleto de um API Gateway é muito simples.

Vamos começar criando uma pasta para o nosso projeto, chamado api-gateway. Dentro dessa pasta crie um arquivo index.js e via terminal, navegue até a pasta do api-gateway e rode o comando ‘npm init’ para inicializar este projeto Node.js.

Configure a sua aplicação através do assistente do npm init para que ele crie o package.json. Agora que temos um package.json, via terminal vamos mandar instalar as dependências que vamos precisar em nosso projeto:

Esse comando vai instalar as seguintes dependências:

  • express: nosso pacote para criar web APIs e web apps facilmente;
  • morgan: o logger que usaremos para saber o que está acontecendo com nosso API Gateway no terminal;
  • helmet: pacote que adiciona proteção a 11 tipos comuns de requisições maliciosas, adicionando alguma segurança ao API Gateway;
  • express-http-proxy: pacote para redirecionar requisições para os microservices;
  • http: módulo básico para criar um servidor HTTP básico;
  • cookie-parser: módulo que não usaremos agora, mas que no futuro será útil para fazer o parsing dos cookies das requisições;

O código abaixo mostra o que é possível fazer em poucas linhas:

Para quem já criou uma API em Express antes não há nada excepcionalmente novo aqui. Eu configurei os pacotes nas primeiras linhas, usei o express-http-proxy para criar proxies de chamadas para dois serviços fake (disponíveis nos fontes no final deste artigo), um de users e outro de products.

A parte de mapeamento das URLs é o mesmo padrão do Express, onde eu redireciono as chamadas /users para o proxy de users e as chamadas de /products para o proxy de products. e no final as configurações do app Express para usar os módulos de logging, segurança, os encodings e o cookie-parser, exatamente nesta ordem.

As duas últimas linhas criam o servidor HTTP para que ele passe a escutar na porta 3000. Note que você terá de subir as suas APIs em outras portas ou até mesmo em outras URLs, deixando a API Gateway como rota default para as chamadas do seu cliente.

#4 – Testando e indo além

Para subir as APIs fake, rode um ‘npm install’ nelas primeiro para instalar as dependências e depois um ‘npm start’ em cada uma, sendo que users está configurada pra rodar na porta 3001, e products na 3002.

Depois que estiver com as duas APIs fake rodando, experimente testá-las no navegador para ver que elas apenas listam sempre os mesmos dados mockados.

Depois, suba o seu api-gateway mandando executar o index.js. Teste o api-gateway diretamente no navegador fazendo as mesmas chamadas que faria aos microservices originais. Se tudo ocorreu bem, o retorno será o mesmo que nos microservices originais, sem qualquer diferença aparente, de forma transparente para o cliente.

E justamente essa é a grande vantagem de se trabalhar com um API Gateway!

Como o Express funciona como um middleware de requisições, é muito simples e prático adicionar camadas adicionais de execução antes de repassar a requisição aos microservices (como segurança com JWT), permitindo todo tipo de gerenciamento de APIs que você imaginar.

Abordagens mais profissionais vão ter o mapeamento parametrizável através de banco de dados para que seja simples de adicionar novas APIs e rotas ao Gateway sem ter de mexer em programação.

De qualquer forma espero que este artigo tenha lhe dado uma ideia de como fazer essa camada importantíssima na arquitetura de microservices.

Muito deste tutorial foi baseado no excelente material da Rising Stack, incluindo os diagramas. Se quiser aprender mais boas práticas com microservices, leia este artigo.

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!