As inscrições já estão abertas para o Microservices de março de 2023. Veja a agenda e inscreva-se aqui .
Editor – Esta série de artigos em sete partes está agora completa:
Você também pode baixar o conjunto completo de artigos, além de informações sobre a implementação de microsserviços usando o NGINX Plus, como um e-book – Microsserviços: Do design à implantação .
Confira também nossa página Soluções de Microsserviços .
Os microsserviços estão recebendo muita atenção atualmente: artigos, blogs, discussões em mídias sociais e apresentações em conferências. Eles estão caminhando rapidamente em direção ao pico de expectativas infladas no ciclo de hype do Gartner . Ao mesmo tempo, há céticos na comunidade de software que descartam os microsserviços como se não fossem nenhuma novidade. Os pessimistas afirmam que a ideia é apenas uma reformulação da marca SOA. No entanto, apesar do exagero e do ceticismo, o padrão de Arquitetura de Microsserviços tem benefícios significativos – especialmente quando se trata de permitir o desenvolvimento e a entrega ágeis de aplicativos empresariais complexos.
Esta postagem do blog é a primeira de uma série de sete partes sobre como projetar, construir e implantar microsserviços . Você aprenderá sobre a abordagem e como ela se compara ao padrão mais tradicional de Arquitetura Monolítica . Esta série descreverá os vários elementos de uma arquitetura de microsserviços. Você aprenderá sobre os benefícios e desvantagens do padrão de Arquitetura de Microsserviços, se ele faz sentido para seu projeto e como aplicá-lo.
Vamos primeiro ver por que você deve considerar o uso de microsserviços.
Vamos imaginar que você estivesse começando a criar um novo aplicativo de táxi com a intenção de competir com o Uber e o Hailo. Após algumas reuniões preliminares e coleta de requisitos, você criaria um novo projeto manualmente ou usando um gerador que vem com Rails, Spring Boot, Play ou Maven. Esta nova aplicação teria uma arquitetura modular hexagonal , como no diagrama a seguir:
No centro do aplicativo está a lógica de negócios, que é implementada por módulos que definem serviços, objetos de domínio e eventos. Ao redor do núcleo há adaptadores que fazem interface com o mundo externo. Exemplos de adaptadores incluem componentes de acesso a banco de dados, componentes de mensagens que produzem e consomem mensagens e componentes da web que expõem APIs ou implementam uma interface de usuário.
Apesar de ter uma arquitetura logicamente modular, o aplicativo é empacotado e implantado como um monólito. O formato real depende da linguagem e da estrutura do aplicativo. Por exemplo, muitos aplicativos Java são empacotados como arquivos WAR e implantados em servidores de aplicativos como Tomcat ou Jetty. Outros aplicativos Java são empacotados como JARs executáveis independentes. Da mesma forma, os aplicativos Rails e Node.js são empacotados como uma hierarquia de diretórios.
Aplicativos escritos nesse estilo são extremamente comuns. Eles são simples de desenvolver, pois nossos IDEs e outras ferramentas são focados na construção de um único aplicativo. Esses tipos de aplicativos também são simples de testar. Você pode implementar testes de ponta a ponta simplesmente iniciando o aplicativo e testando a interface do usuário com o Selenium. Aplicações monolíticas também são simples de implantar. Você só precisa copiar o aplicativo empacotado para um servidor. Você também pode dimensionar o aplicativo executando várias cópias por trás de um balanceador de carga. Funciona bem nos estágios iniciais do projeto.
Infelizmente, essa abordagem simples tem uma enorme limitação. Os aplicativos bem-sucedidos tendem a crescer com o tempo e, eventualmente, se tornar enormes. Durante cada sprint, sua equipe de desenvolvimento implementa mais algumas histórias, o que, é claro, significa adicionar muitas linhas de código. Depois de alguns anos, seu aplicativo pequeno e simples terá se transformado em um monstruoso monólito . Para dar um exemplo extremo, conversei recentemente com um desenvolvedor que estava escrevendo uma ferramenta para analisar as dependências entre os milhares de JARs em seu aplicativo de vários milhões de linhas de código (LOC). Tenho certeza de que foi necessário o esforço conjunto de um grande número de desenvolvedores ao longo de muitos anos para criar uma fera dessas.
Quando seu aplicativo se torna um monólito grande e complexo, sua organização de desenvolvimento provavelmente está em apuros. Qualquer tentativa de desenvolvimento e entrega ágil fracassará. Um grande problema é que a aplicação é extremamente complexa. É simplesmente grande demais para qualquer desenvolvedor entender completamente. Como resultado, corrigir bugs e implementar novos recursos corretamente se torna difícil e demorado. Além disso, isso tende a ser uma espiral descendente. Se a base de código for difícil de entender, as alterações não serão feitas corretamente. Você vai acabar com uma bola de lama monstruosa e incompreensível.
O tamanho do aplicativo também tornará o desenvolvimento mais lento. Quanto maior o aplicativo, maior será o tempo de inicialização. Por exemplo, em uma pesquisa recente, alguns desenvolvedores relataram tempos de inicialização de até 12 minutos. Também ouvi histórias de aplicativos que demoram até 40 minutos para iniciar. Se os desenvolvedores tiverem que reiniciar o servidor de aplicativos regularmente, grande parte do dia será gasta esperando e sua produtividade será prejudicada.
Outro problema com um aplicativo monolítico grande e complexo é que ele é um obstáculo à implantação contínua. Hoje em dia, o que há de mais moderno em aplicativos SaaS é enviar alterações para produção várias vezes ao dia. Isso é extremamente difícil de fazer com um monólito complexo, pois você precisa reimplantar o aplicativo inteiro para atualizar qualquer parte dele. Os longos tempos de inicialização que mencionei anteriormente também não ajudarão. Além disso, como o impacto de uma mudança geralmente não é muito bem compreendido, é provável que você tenha que fazer testes manuais extensivos. Consequentemente, a implantação contínua é quase impossível de ser feita.
Aplicações monolíticas também podem ser difíceis de escalar quando diferentes módulos têm requisitos de recursos conflitantes. Por exemplo, um módulo pode implementar lógica de processamento de imagem com uso intensivo de CPU e, idealmente, seria implantado em instâncias otimizadas para computação do Amazon EC2 . Outro módulo pode ser um banco de dados na memória e mais adequado para instâncias otimizadas de memória do EC2 . No entanto, como esses módulos são implantados juntos, você precisa fazer concessões na escolha do hardware.
Outro problema com aplicações monolíticas é a confiabilidade. Como todos os módulos são executados no mesmo processo, um bug em qualquer módulo, como um vazamento de memória, pode potencialmente derrubar todo o processo. Além disso, como todas as instâncias do aplicativo são idênticas, esse bug afetará a disponibilidade de todo o aplicativo.
Por último, mas não menos importante, aplicativos monolíticos tornam extremamente difícil adotar novas estruturas e linguagens. Por exemplo, vamos imaginar que você tem 2 milhões de linhas de código escritas usando o framework XYZ. Seria extremamente caro (em tempo e custo) reescrever todo o aplicativo para usar a nova estrutura ABC, mesmo que essa estrutura fosse consideravelmente melhor. Como resultado, há uma enorme barreira para a adoção de novas tecnologias. Você fica preso às escolhas tecnológicas que fez no início do projeto.
Para resumir: você tem um aplicativo crítico de negócios bem-sucedido que se tornou um monstruoso monstruoso que muito poucos desenvolvedores, se houver algum, entendem. Ele é escrito usando tecnologia obsoleta e improdutiva, o que dificulta a contratação de desenvolvedores talentosos. O aplicativo é difícil de escalar e não é confiável. Como resultado, o desenvolvimento e a entrega ágeis de aplicativos são impossíveis.
Então o que você pode fazer a respeito?
Muitas organizações, como Amazon, eBay e Netflix , resolveram esse problema adotando o que hoje é conhecido como padrão de Arquitetura de Microsserviços . Em vez de criar um único aplicativo monstruoso e monolítico, a ideia é dividir seu aplicativo em um conjunto de serviços menores e interconectados.
Um serviço normalmente implementa um conjunto de recursos ou funcionalidades distintas, como gerenciamento de pedidos, gerenciamento de clientes, etc. Cada microsserviço é um miniaplicativo que tem sua própria arquitetura hexagonal, composta de lógica de negócios e vários adaptadores. Alguns microsserviços exporiam uma API que é consumida por outros microsserviços ou pelos clientes do aplicativo. Outros microsserviços podem implementar uma UI da web. Em tempo de execução, cada instância é frequentemente uma VM de nuvem ou um contêiner Docker.
Por exemplo, uma possível decomposição do sistema descrito anteriormente é mostrada no diagrama a seguir:
Cada área funcional do aplicativo agora é implementada por seu próprio microsserviço. Além disso, o aplicativo web é dividido em um conjunto de aplicativos web mais simples (como um para passageiros e outro para motoristas em nosso exemplo de chamada de táxi). Isso facilita a implantação de experiências distintas para usuários, dispositivos ou casos de uso especializados específicos.
Cada serviço de backend expõe uma API REST e a maioria dos serviços consome APIs fornecidas por outros serviços. Por exemplo, o Gerenciamento de Motoristas usa o servidor de Notificação para informar um motorista disponível sobre uma possível viagem. Os serviços de interface do usuário invocam os outros serviços para renderizar páginas da web. Os serviços também podem usar comunicação assíncrona baseada em mensagens. A comunicação entre serviços será abordada com mais detalhes posteriormente nesta série.
Algumas APIs REST também são expostas aos aplicativos móveis usados pelos motoristas e passageiros. No entanto, os aplicativos não têm acesso direto aos serviços de backend. Em vez disso, a comunicação é mediada por um intermediário conhecido como API Gateway . O API Gateway é responsável por tarefas como balanceamento de carga, cache, controle de acesso, medição de API e monitoramento, e pode ser implementado efetivamente usando NGINX . Artigos posteriores da série abordarão o API gateway .
O padrão de Arquitetura de Microsserviços corresponde à escala do eixo Y do Scale Cube , que é um modelo 3D de escalabilidade do excelente livro The Art of Scalability . Os outros dois eixos de dimensionamento são o dimensionamento do eixo X, que consiste em executar várias cópias idênticas do aplicativo por trás de um balanceador de carga, e o dimensionamento do eixo Z (ou particionamento de dados), em que um atributo da solicitação (por exemplo, a chave primária de uma linha ou a identidade de um cliente) é usado para rotear a solicitação para um servidor específico.
Os aplicativos normalmente usam os três tipos de dimensionamento juntos. O dimensionamento do eixo Y decompõe o aplicativo em microsserviços, conforme mostrado acima na primeira figura desta seção. No tempo de execução, o dimensionamento do eixo X executa várias instâncias de cada serviço por trás de um balanceador de carga para rendimento e disponibilidade. Alguns aplicativos também podem usar o dimensionamento do eixo Z para particionar os serviços. O diagrama a seguir mostra como o serviço Trip Management pode ser implantado com o Docker em execução no Amazon EC2.
Em tempo de execução, o serviço Trip Management consiste em várias instâncias de serviço. Cada instância de serviço é um contêiner do Docker. Para serem altamente disponíveis, os contêineres estão sendo executados em várias VMs de nuvem. Na frente das instâncias de serviço, há um balanceador de carga, como o NGINX , que distribui solicitações entre as instâncias. O balanceador de carga também pode lidar com outras questões, como cache , controle de acesso , medição de API e monitoramento .
O padrão de arquitetura de microsserviços impacta significativamente o relacionamento entre o aplicativo e o banco de dados. Em vez de compartilhar um único esquema de banco de dados com outros serviços, cada serviço tem seu próprio esquema de banco de dados. Por um lado, essa abordagem está em desacordo com a ideia de um modelo de dados para toda a empresa. Além disso, isso geralmente resulta na duplicação de alguns dados. No entanto, ter um esquema de banco de dados por serviço é essencial se você deseja se beneficiar dos microsserviços, porque isso garante um acoplamento flexível. O diagrama a seguir mostra a arquitetura do banco de dados para o aplicativo de exemplo.
Cada um dos serviços tem seu próprio banco de dados. Além disso, um serviço pode usar um tipo de banco de dados mais adequado às suas necessidades, a chamada arquitetura de persistência poliglota. Por exemplo, o Driver Management, que encontra motoristas próximos a um possível passageiro, deve usar um banco de dados que suporte consultas geográficas eficientes.
Superficialmente, o padrão de Arquitetura de Microsserviços é semelhante ao SOA. Com ambas as abordagens, a arquitetura consiste em um conjunto de serviços. No entanto, uma maneira de pensar sobre o padrão de Arquitetura de Microsserviços é que ele é SOA sem a comercialização e a bagagem percebida das especificações de serviços web (WS‑*) e um Barramento de Serviços Empresariais (ESB). Os aplicativos baseados em microsserviços preferem protocolos mais simples e leves, como REST, em vez de WS‑*. Eles também evitam muito usar ESBs e, em vez disso, implementam funcionalidades semelhantes a ESB nos próprios microsserviços. O padrão de arquitetura de microsserviços também rejeita outras partes do SOA, como o conceito de esquema canônico.
O padrão de arquitetura de microsserviços tem uma série de benefícios importantes. Primeiro, ele aborda o problema da complexidade. Ele decompõe o que de outra forma seria um aplicativo monolítico monstruoso em um conjunto de serviços. Embora a quantidade total de funcionalidades permaneça inalterada, o aplicativo foi dividido em partes ou serviços gerenciáveis. Cada serviço tem um limite bem definido na forma de uma RPC ou API orientada a mensagens. O padrão Microservices Architecture impõe um nível de modularidade que, na prática, é extremamente difícil de atingir com uma base de código monolítica. Consequentemente, serviços individuais são muito mais rápidos de desenvolver e muito mais fáceis de entender e manter.
Em segundo lugar, essa arquitetura permite que cada serviço seja desenvolvido de forma independente por uma equipe focada naquele serviço. Os desenvolvedores são livres para escolher quaisquer tecnologias que façam sentido, desde que o serviço honre o contrato da API. É claro que a maioria das organizações gostaria de evitar a anarquia completa e limitar as opções tecnológicas. No entanto, essa liberdade significa que os desenvolvedores não são mais obrigados a usar tecnologias possivelmente obsoletas que existiam no início de um novo projeto. Ao escrever um novo serviço, eles têm a opção de usar a tecnologia atual. Além disso, como os serviços são relativamente pequenos, torna-se viável reescrever um serviço antigo usando a tecnologia atual.
Terceiro, o padrão de Arquitetura de Microsserviços permite que cada microsserviço seja implantado de forma independente. Os desenvolvedores nunca precisam coordenar a implantação de mudanças que são locais em seus serviços. Esses tipos de mudanças podem ser implementadas assim que forem testadas. A equipe de IU pode, por exemplo, realizar testes A/B e iterar rapidamente nas alterações da IU. O padrão de arquitetura de microsserviços torna possível a implantação contínua.
Por fim, o padrão de Arquitetura de Microsserviços permite que cada serviço seja dimensionado de forma independente. Você pode implantar apenas o número de instâncias de cada serviço que satisfaça suas restrições de capacidade e disponibilidade. Além disso, você pode usar o hardware que melhor atende aos requisitos de recursos de um serviço. Por exemplo, você pode implantar um serviço de processamento de imagem com uso intensivo de CPU em instâncias do EC2 Compute Optimized e implantar um serviço de banco de dados na memória em instâncias do EC2 Memory Optimized.
Como Fred Brooks escreveu há quase 30 anos, não existem soluções milagrosas. Como qualquer outra tecnologia, a arquitetura de microsserviços tem desvantagens. Uma desvantagem é o próprio nome. O termo microsserviço dá ênfase excessiva ao tamanho do serviço. Na verdade, há alguns desenvolvedores que defendem a construção de serviços LOC de 10 a 100 extremamente detalhados. Embora serviços pequenos sejam preferíveis, é importante lembrar que eles são um meio para um fim e não o objetivo principal. O objetivo dos microsserviços é decompor suficientemente o aplicativo para facilitar o desenvolvimento e a implantação ágeis do aplicativo.
Outra grande desvantagem dos microsserviços é a complexidade que surge do fato de que um aplicativo de microsserviços é um sistema distribuído. Os desenvolvedores precisam escolher e implementar um mecanismo de comunicação entre processos com base em mensagens ou RPC. Além disso, eles também devem escrever código para lidar com falhas parciais, já que o destino de uma solicitação pode ser lento ou indisponível. Embora nada disso seja ciência de foguetes, é muito mais complexo do que em um aplicativo monolítico onde os módulos invocam uns aos outros por meio de chamadas de método/procedimento em nível de linguagem.
Outro desafio com microsserviços é a arquitetura de banco de dados particionado. Transações comerciais que atualizam diversas entidades comerciais são bastante comuns. Esses tipos de transações são fáceis de implementar em um aplicativo monolítico porque há um único banco de dados. Em um aplicativo baseado em microsserviços, no entanto, você precisa atualizar vários bancos de dados de propriedade de serviços diferentes. Usar transações distribuídas geralmente não é uma opção, e não apenas por causa do teorema CAP . Eles simplesmente não são suportados por muitos dos bancos de dados NoSQL e corretores de mensagens altamente escaláveis de hoje em dia. Você acaba tendo que usar uma abordagem baseada em consistência eventual, o que é mais desafiador para desenvolvedores.
Testar um aplicativo de microsserviços também é muito mais complexo. Por exemplo, com um framework moderno como o Spring Boot é trivial escrever uma classe de teste que inicia um aplicativo web monolítico e testa sua API REST. Em contraste, uma classe de teste similar para um serviço precisaria iniciar esse serviço e quaisquer serviços dos quais ele dependa (ou pelo menos configurar stubs para esses serviços). Mais uma vez, isso não é ciência de foguetes, mas é importante não subestimar a complexidade de fazer isso.
Outro grande desafio com o padrão de Arquitetura de Microsserviços é implementar mudanças que abrangem múltiplos serviços. Por exemplo, vamos imaginar que você está implementando uma história que requer mudanças nos serviços A, B e C, onde A depende de B e B depende de C. Em um aplicativo monolítico, você poderia simplesmente alterar os módulos correspondentes, integrar as mudanças e implantá-las de uma só vez. Por outro lado, em um padrão de Arquitetura de Microsserviços, você precisa planejar e coordenar cuidadosamente a implementação de mudanças em cada um dos serviços. Por exemplo, você precisaria atualizar o serviço C, seguido pelo serviço B e, finalmente, o serviço A. Felizmente, a maioria das alterações normalmente afeta apenas um serviço, e alterações em vários serviços que exigem coordenação são relativamente raras.
A implantação de um aplicativo baseado em microsserviços também é muito mais complexa. Um aplicativo monolítico é simplesmente implantado em um conjunto de servidores idênticos por trás de um balanceador de carga tradicional. Cada instância do aplicativo é configurada com os locais (host e portas) dos serviços de infraestrutura, como o banco de dados e um agente de mensagens. Em contraste, um aplicativo de microsserviço normalmente consiste em um grande número de serviços. Por exemplo, a Hailo tem 160 serviços diferentes e a Netflix tem mais de 600, de acordo com Adrian Cockcroft [Editor – A Hailo foi adquirida pela MyTaxi.] . Cada serviço terá várias instâncias de tempo de execução. São muito mais peças móveis que precisam ser configuradas, implantadas, dimensionadas e monitoradas. Além disso, você também precisará implementar um mecanismo de descoberta de serviço (discutido em uma postagem posterior) que permita que um serviço descubra os locais (hosts e portas) de quaisquer outros serviços com os quais ele precise se comunicar. Abordagens tradicionais de operações manuais e baseadas em tickets de problemas não podem ser dimensionadas para esse nível de complexidade. Consequentemente, a implantação bem-sucedida de um aplicativo de microsserviços exige maior controle dos métodos de implantação pelos desenvolvedores e um alto nível de automação.
Uma abordagem para automação é usar um PaaS pronto para uso, como o Cloud Foundry . Um PaaS oferece aos desenvolvedores uma maneira fácil de implantar e gerenciar seus microsserviços. Isso os isola de preocupações como aquisição e configuração de recursos de TI. Ao mesmo tempo, os profissionais de sistemas e redes que configuram o PaaS podem garantir a conformidade com as melhores práticas e com as políticas da empresa. Outra maneira de automatizar a implantação de microsserviços é desenvolver o que é essencialmente seu próprio PaaS. Um ponto de partida típico é usar uma solução de cluster, como o Kubernetes , em conjunto com uma tecnologia como o Docker. Mais adiante nesta série, veremos como abordagens de entrega de aplicativos baseadas em software , como o NGINX Plus, que lida facilmente com cache, controle de acesso, medição de API e monitoramento no nível de microsserviços, podem ajudar a resolver esse problema.
Criar aplicativos complexos é inerentemente difícil. Uma arquitetura monolítica só faz sentido para aplicações simples e leves. Você acabará em um mundo de problemas se usá-lo para aplicações complexas. O padrão de arquitetura de microsserviços é a melhor escolha para aplicativos complexos e em evolução, apesar das desvantagens e desafios de implementação.
Em postagens de blog posteriores, vou me aprofundar nos detalhes de vários aspectos do padrão de arquitetura de microsserviços e discutir tópicos como descoberta de serviços, opções de implantação de serviços e estratégias para refatorar um aplicativo monolítico em serviços.
Fique atento…
Editor – Esta série de artigos em sete partes está agora completa:
Você também pode baixar o conjunto completo de artigos, além de informações sobre a implementação de microsserviços usando o NGINX Plus, como um e-book – Microsserviços: Do design à implantação .
O blogueiro convidado Chris Richardson é o fundador do CloudFoundry.com original, um dos primeiros Java PaaS (Plataforma como Serviço) para Amazon EC2. Agora, ele presta consultoria a organizações para melhorar a maneira como elas desenvolvem e implantam aplicativos.
"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."