Até o presente momento, eu já escrevi alguns artigos sobre a arquitetura de micro serviços (micro services architecture), uma proposta de arquitetura antagônica aos tradicionais monolitos, ou grandes blocos de software que são o tipo mais comum de arquitetura presente atualmente nas empresas. Eu já estudava e gostava bastante deste modelo de arquitetura, que não é exatamente nova, mas sim uma releitura de arquiteturas passadas, porém, depois de passar a trabalhar no Agibank e ver os ganhos que temos com essa abordagem no dia a dia, eu realmente fui convertido em um evangelista de micro serviços nas empresas.
Resumidamente, um micro serviço é uma API; um backend que responde a uma única e exclusiva função, pautado no clássico SRP do SOLID (Single Responsibility Principle – Princípio da Responsabilidade Única). Cada micro serviço é independente dos outros e não possui qualquer tipo de dependência, tendo até mesmo a sua própria base de dados e servidor web em muitos casos, uma vez que esse é o único cenário real de 100% independência.
Obviamente, sendo tão “micro” em sua responsabilidade, pouco se faz com apenas um microservice em um sistema inteiro. Dessa forma, é na chamada a múltiplos micro services que os comportamentos dos sistemas complexos são construídos; pautados em princípios como tolerância à falhas, containerização e escala horizontal.
A Amazon é um símbolo dessa proposta, uma vez que alegam ter mais de 100 micro serviços sendo chamados durante a exibição de suas páginas de produtos no site Amazon.com. Outro ícone dessa abordagem é a Netflix, que tem um serviço responsável por tentar derrubar seus micro serviços de maneira aleatória para testar sua resiliência, tolerância à falhas e interdependência entre eles.
E por fim, temos os testes e o deploy. É incrivelmente mais fácil ter uma cobertura de testes unitários automatizados que garantam o funcionamento de um sistema completo se eu tiver quebrado ele em micro-pedaços, assim como o deploy, que se torna bem menos oneroso e mais ágil neste contexto, pode fazer implantação de micro services de maneira independente, desde que os contratos sejam mantidos intactos ou através de um sistema rigoroso de versionamento e APIs.
Ok, mas essa teoria eu já falei em outras oportunidades aqui no portal, certo? Mas como se cria um micro service na prática?
A ideia deste artigo é justamente é essa, mostrar como você cria um micro service profissional usando a plataforma do Node.js, uma das mais utilizadas neste tipo de abordagem (Netflix, LinkedIn, PayPal, etc). Em Java você poderia fazer algo semelhante também, usando Spring Boot, mas deixo isso para outra oportunidade.
Por que Node.js?
Lembra quando falei que a ideia é que o micro serviço seja independente do restante da aplicação? Pois é, uma das coisas que te garante independência é ter micro services rodando de maneira standalone, ou seja, em suas próprias plataformas.
Rodar microservices em Node.js permite que cada um deles tenha a sua própria instância de Node sem que isso pese muito no custo da empresa, uma vez que rodar Node é extremamente barato. Além de ser extremamente rápido de criar serviços standalone em Node, seja a partir do clássico exemplo de servidor em 12 linhas que está no site oficial ou usando web frameworks como ExpressJS (meu favorito).
Caso não esteja familiarizado com Node.js, ExpressJS e cia., recomendo ler os outros artigos do meu portal ou meu livro.
Por que MongoDB?
Idealmente, cada serviço tem o seu próprio banco de dados para que seja realmente independente dos demais, sendo o domínio de aplicação extremamente enxuto, o banco de dados do micro service também o será. Uma coisa que eu acho extremamente positiva no uso de micro services com bancos independentes, principalmente em empresas que usam métodos ágeis, é que cada serviço pode ter a tecnologia de banco de dados mais adequado para sua função.
Assim, um micro service de catálogo de produtos pode usar MongoDB, enquanto que no mesmo sistema, o micro service de pagamento pode usar um SQL Server, enquanto os sistemas estatísticos da empresa estão em um Cassandra. Óbvio que manter uma estrutura dessas em pé requer um time de engenharia forte, mas esse tipo de liberdade permite que os serviços sejam o mais eficientes possível e que os times que trabalham neles não fiquem presos a uma única suíte tecnológica, sendo autônomos e auto-organizados como o ágil prega.
Neste contexto, o MongoDB é uma excelente opção, na minha opinião, uma vez que sua natureza já prega a independência dos documentos, mesmo entre as coleções do mesmo banco. Assim, conseguimos criar bancos poderosos, que escalam muito bem horizontalmente (um requisito muito comum em abordagens de micro serviços) e ao mesmo tempo simples, geralmente tendo uma coleção apenas.
Eu particularmente sou suspeito pra falar (afinal, sou autor de livros sobre ambas plataformas), mas acho que Node e Mongo tem um fit absurdamente bom para compor soluções. Se você não sabe nada de MongoDB, sugiro ler os outros artigos sobre o assunto no meu site ou meu livro.
O que não abordarei?
Não irei falar de DevOps aqui, e consequentemente de deploy, CI, CD, etc. Quero me ater ao desenvolvimento do micro serviço sem ter de explicar Docker, API Gateway, etc.
Também não falarei de front-end – isso é meio óbvio, mas realmente não teremos interface gráfica neste artigo. Primeiro porque não é meu intuito, segundo que não sou reconhecido pelos meus talentos com frameworks front-end.
Não falarei dos princípios de desenvolvimento de web APIs RESTful, o que sugiro que busque em outros materiais do meu site ou em meu livro de Programação Web com Node.js. Mas sim, usaremos estes princípios ao longo deste artigo. E não falarei de versionamento ou documentação de APIs também (Swagger por exemplo).
Case de exemplo: Cinema
Imagine que você está trabalhando no departamento de TI de uma grande rede de cinemas (ideia nem um pouco original, admito) que deseja refatorar todo o seu sistema de bilheteria e bomboniere de um gigantesco monolito para uma arquitetura de micro services. Como essa empreitada seria enorme para ser discutida apenas em um artigo, vamos focar aqui no serviço de filmes, dentro da arquitetura abaixo.
Note que temos as responsabilidades já separadas neste desenho, dando um exemplo real de uma arquitetura usando micro serviços. Enquanto que em uma estrutura monolítica tradicional teríamos algo como um único webservice para o sistema de cinema inteiro (quem nunca fez um api.empresa.com na sua carreira?) ou no máximo dois, um para a bomboniere (grocery store) e outro para o sistema de ingressos em si (booking).
Pensando em um dos casos de uso (ou histórias de usuário) mais comuns em um sistema de cinema, temos o cliente que deseja ver os lançamentos no cinema mais próximo da sua casa. O diagrama abaixo ilustra um front-end mobile (app) consultando um serviço de cinemas que por sua vez consulta um serviço de filmes.
Por que dois serviços para entregar os lançamentos do cinema para o usuário?
Essa é a pergunta número um de quem sempre construiu webservices monolíticos na sua vida. A resposta é: segregação de responsabilidades (SRP, lembra?). O serviço de filmes (movies) tem apenas a preocupação de expor dados de filmes conforme consultado, sem qualquer regra de negócio ou julgamento quanto às chamadas. Já o serviço de catálogo do cinema (cinema catalog) é responsável pelas informações das salas de cinema existentes na rede e suas agendas.
Mas isso não é ineficiente, uma vez que temos de fazer sub-chamadas de serviços para atender a uma simples requisição do front-end?
Essa é a pergunta #2 de quem sempre buscou responder “desejos” do front-end em uma única chamada ao servidor. O engraçado é que eu recebo o mesmo tipo de indagação quando ensino programação orientada à objetos para quem está acostumado com procedural. Não são os milissegundos a mais que as sub-chamadas (seja entre serviços ou entre objetos) possuem que vão fazer a diferença na experiência de performance do sistema como um todo.
Como este artigo está ficando extenso, vou iniciar o desenvolvimento destes micro services na próxima parte desta série.