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 essas e outras questões sem gambiarra, de maneira corporativa? Usando um API Gateway!
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 client diferente, como na imagem abaixo:
Uma solução bem comum no mercado é usar um proxy reverso, como nginx, mas o Node.js também é uma 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:
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 artigo é fazer um API Gateway simples em Node.js, seguindo a arquitetura abaixo:
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 hora o serviço antigo, hora os novos microservices. O diagrama abaixo mostra isso:
Mais pra frente podemos aproveitar essa 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 esse 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 artigo, eu inclui dois microsserviços fake que sempre retornam os mesmos dados mockados para que você possa testar. No entanto, o mais recomendado é que você dê uma olhada nos artigos de microservices que estão aqui no portal para realmente aprender o tópico completo.
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 precisaremos em nosso projeto:
npm install http express morgan helmet express-http-proxy cookie-parser
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:
//index.js var http = require('http'); const express = require('express') const httpProxy = require('express-http-proxy') const app = express() var cookieParser = require('cookie-parser'); var logger = require('morgan'); const helmet = require('helmet'); const userServiceProxy = httpProxy('http://localhost:3001'); const productsServiceProxy = httpProxy('http://localhost:3002'); // Proxy request app.get('/users', (req, res, next) => { userServiceProxy(req, res, next); }) app.get('/products', (req, res, next) => { productsServiceProxy(req, res, next); }) app.use(logger('dev')); app.use(helmet()); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); var server = http.createServer(app); server.listen(3000);
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 esse artigo tenha lhe dado uma ideia de como fazer essa camada importantíssima na arquitetura de microservices.
Muito deste artigo foi baseado no excelente material da Rising Stack, incluindo os diagramas.
Até a próxima!