Compartilhar via


Criar um aplicativo orientado a microsserviços

Dica

Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, disponível em do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

miniatura da capa do eBook sobre arquitetura de microsserviços do .NET para aplicativos .NET em contêineres.

Esta seção se concentra no desenvolvimento de um aplicativo empresarial hipotético do lado do servidor.

Especificações do aplicativo

O aplicativo hipotético lida com solicitações executando a lógica de negócios, acessando bancos de dados e retornando respostas HTML, JSON ou XML. Vamos dizer que o aplicativo deve dar suporte a vários clientes, incluindo navegadores de área de trabalho que executam SPAs (Aplicativos de Página Única), aplicativos Web tradicionais, aplicativos Web móveis e aplicativos móveis nativos. O aplicativo também pode expor uma API para que terceiros consumam. Ele também deve ser capaz de integrar seus microsserviços ou aplicativos externos de forma assíncrona, de modo que essa abordagem ajudará a resiliência dos microsserviços no caso de falhas parciais.

O aplicativo consistirá nesses tipos de componentes:

  • Componentes de apresentação. Esses componentes são responsáveis por lidar com a interface do usuário e consumir serviços remotos.

  • Lógica de domínio ou de negócios. Esse componente é a lógica de domínio do aplicativo.

  • Lógica de acesso ao banco de dados. Esse componente consiste em componentes de acesso a dados responsáveis por acessar bancos de dados (SQL ou NoSQL).

  • Lógica de integração de aplicativos. Esse componente inclui um canal de mensagens, com base em agentes de mensagens.

O aplicativo exigirá alta escalabilidade, permitindo que seus subsistemas verticais sejam dimensionados de forma autônoma, pois determinados subsistemas exigirão mais escalabilidade do que outros.

O aplicativo deve ser capaz de ser implantado em vários ambientes de infraestrutura (várias nuvens públicas e locais) e, idealmente, deve ser multiplataforma, capaz de mover do Linux para o Windows (ou vice-versa) facilmente.

Contexto da equipe de desenvolvimento

Também presumimos o seguinte sobre o processo de desenvolvimento para o aplicativo:

  • Você tem várias equipes de desenvolvimento com foco em diferentes áreas de negócios do aplicativo.

  • Os novos membros da equipe devem se tornar produtivos rapidamente e o aplicativo deve ser fácil de entender e modificar.

  • O aplicativo terá uma evolução de longo prazo e regras de negócios em constante mudança.

  • Você precisa de uma boa manutenção a longo prazo, o que significa ter agilidade ao implementar novas alterações no futuro, ao mesmo tempo em que pode atualizar vários subsistemas com impacto mínimo sobre os outros subsistemas.

  • Você deseja praticar a integração contínua e a implantação contínua do aplicativo.

  • Você deseja aproveitar as tecnologias emergentes (estruturas, linguagens de programação etc.) durante a evolução do aplicativo. Você não deseja fazer migrações completas do aplicativo ao migrar para novas tecnologias, pois isso resultaria em altos custos e afetaria a previsibilidade e a estabilidade do aplicativo.

Escolhendo uma arquitetura

Qual deve ser a arquitetura de implantação do aplicativo? As especificações do aplicativo, juntamente com o contexto de desenvolvimento, sugerem fortemente que você deve arquitetar o aplicativo decompondo-o em subsistemas autônomos na forma de microsserviços e contêineres colaboradores, em que um microsserviço é um contêiner.

Nessa abordagem, cada serviço (contêiner) implementa um conjunto de funções coesas e estreitamente relacionadas. Por exemplo, um aplicativo pode consistir em serviços como o serviço de catálogo, serviço de ordenação, serviço de cesta, serviço de perfil do usuário etc.

Os microsserviços se comunicam usando protocolos como HTTP (REST), mas também de forma assíncrona (por exemplo, usando AMQP) sempre que possível, especialmente ao propagar atualizações com eventos de integração.

Os microsserviços são desenvolvidos e implantados como contêineres independentemente uns dos outros. Essa abordagem significa que uma equipe de desenvolvimento pode estar desenvolvendo e implantando um determinado microsserviço sem afetar outros subsistemas.

Cada microsserviço tem seu próprio banco de dados, permitindo que ele seja totalmente dissociado de outros microsserviços. Quando necessário, a consistência entre bancos de dados de microsserviços diferentes é obtida usando eventos de integração no nível do aplicativo (por meio de um barramento de eventos lógicos), conforme tratado em CQRS (Segregação de Responsabilidade de Comando e Consulta). Por isso, as restrições comerciais devem incorporar a consistência eventual entre os vários microsserviços e bancos de dados relacionados.

eShopOnContainers: um aplicativo de referência para .NET e microsserviços implantados usando contêineres

Para que você possa se concentrar na arquitetura e nas tecnologias em vez de pensar em um domínio de negócios hipotético que talvez você não conheça, selecionamos um domínio comercial conhecido, ou seja, um aplicativo simplificado de comércio eletrônico (e-shop) que apresenta um catálogo de produtos, recebe pedidos de clientes, verifica inventário e executa outras funções de negócios. Esse código-fonte do aplicativo baseado em contêiner está disponível no repositório GitHub do eShopOnContainers .

O aplicativo consiste em vários subsistemas, incluindo vários front-ends de interface do usuário da loja (um aplicativo Web e um aplicativo móvel nativo), juntamente com os microsserviços e contêineres de back-end para todas as operações necessárias do lado do servidor com vários Gateways de API como pontos de entrada consolidados para os microsserviços internos. A Figura 6-1 mostra a arquitetura do aplicativo de referência.

Diagrama de aplicativos cliente usando eShopOnContainers em um único host do Docker.

Figura 6-1. A arquitetura de aplicativo de referência do eShopOnContainers para o ambiente de desenvolvimento

O diagrama acima mostra que os clientes móveis e SPA se comunicam com pontos de entrada de gateway de API únicos, que então se comunicam com os microsserviços. Os clientes Web tradicionais se comunicam com o microsserviço MVC, que se comunica com microsserviços por meio do gateway de API.

Ambiente de hospedagem. Na Figura 6-1, você vê vários contêineres implantados em um único host do Docker. Esse seria o caso ao implantar em um único host do Docker com o comando docker-compose up. No entanto, se você estiver usando um orquestrador ou cluster de contêineres, cada contêiner poderá estar em execução em um host (nó) diferente e qualquer nó poderá estar executando qualquer número de contêineres, como explicamos anteriormente na seção de arquitetura.

Arquitetura de comunicação. O aplicativo eShopOnContainers usa dois tipos de comunicação, dependendo do tipo de ação funcional (consultas versus atualizações e transações):

  • Comunicação entre cliente HTTP e microsserviço por meio de gateways de API. Essa abordagem é usada para consultas e ao aceitar comandos de atualização ou transacionais dos aplicativos cliente. A abordagem usando Gateways de API é explicada em detalhes em seções posteriores.

  • Comunicação assíncrona baseada em evento. Essa comunicação ocorre por meio de um barramento de eventos para propagar atualizações entre microsserviços ou integrar-se a aplicativos externos. O barramento de eventos pode ser implementado com qualquer tecnologia de infraestrutura de agente de mensagens, como RabbitMQ, ou usando barramentos de serviços de nível mais alto (nível de abstração), como Barramento de Serviço do Azure, NServiceBus, MassTransit ou Brighter.

O aplicativo é implantado como um conjunto de microsserviços na forma de contêineres. Os aplicativos cliente podem se comunicar com esses microsserviços em execução como contêineres por meio das URLs públicas publicadas pelos Gateways de API.

Soberania de dados por microsserviço

No aplicativo de exemplo, cada microsserviço possui seu próprio banco de dados ou fonte de dados, embora todos os bancos de dados do SQL Server sejam implantados como um único contêiner. Essa decisão de design foi tomada apenas para facilitar que um desenvolvedor obtenha o código do GitHub, clone-o e abra-o no Visual Studio ou no Visual Studio Code. Ou, como alternativa, facilita a compilação das imagens personalizadas do Docker usando a CLI do .NET e a CLI do Docker e, em seguida, implantá-las e executá-las em um ambiente de desenvolvimento do Docker. De qualquer forma, o uso de contêineres para fontes de dados permite que os desenvolvedores criem e implantem em questão de minutos sem precisar provisionar um banco de dados externo ou qualquer outra fonte de dados com dependências rígidas na infraestrutura (nuvem ou local).

Em um ambiente de produção real, para alta disponibilidade e escalabilidade, os bancos de dados devem ser baseados em servidores de banco de dados na nuvem ou no local, mas não em contêineres.

Portanto, as unidades de implantação para microsserviços (e até mesmo para bancos de dados neste aplicativo) são contêineres do Docker, e o aplicativo de referência é um aplicativo de vários contêineres que adota princípios de microsserviços.

Recursos adicionais

Benefícios de uma solução baseada em microsserviço

Uma solução baseada em microsserviço como esta tem muitos benefícios:

Cada microsserviço é relativamente pequeno, fácil de gerenciar e evoluir. Especificamente:

  • É fácil para um desenvolvedor entender e começar rapidamente com uma boa produtividade.

  • Os contêineres iniciam rapidamente, o que torna os desenvolvedores mais produtivos.

  • Um IDE como o Visual Studio pode carregar projetos menores rapidamente, tornando os desenvolvedores produtivos.

  • Cada microsserviço pode ser projetado, desenvolvido e implantado independentemente de outros microsserviços, o que proporciona agilidade porque é mais fácil implantar novas versões de microsserviços com frequência.

É possível escalar horizontalmente áreas individuais do aplicativo. Por exemplo, o serviço de catálogo ou o serviço de cesta pode precisar ser dimensionado, mas não o processo de pedidos. Uma infraestrutura de microsserviços será muito mais eficiente em relação aos recursos usados ao escalar horizontalmente do que seria uma arquitetura monolítica.

Você pode dividir o trabalho de desenvolvimento entre várias equipes. Cada serviço pode pertencer a uma única equipe de desenvolvimento. Cada equipe pode gerenciar, desenvolver, implantar e dimensionar seu serviço independentemente do restante das equipes.

Os problemas são mais isolados. Se houver um problema em um serviço, somente esse serviço será inicialmente afetado (exceto quando o design errado for usado, com dependências diretas entre microsserviços) e outros serviços poderão continuar a lidar com solicitações. Por outro lado, um componente com mau funcionamento em uma arquitetura de implantação monolítica pode derrubar todo o sistema, especialmente quando envolve recursos, como um vazamento de memória. Além disso, quando um problema em um microsserviço é resolvido, você pode implantar apenas o microsserviço afetado sem afetar o restante do aplicativo.

Você pode usar as tecnologias mais recentes. Como você pode começar a desenvolver serviços de forma independente e executá-los lado a lado (graças aos contêineres e ao .NET), você pode começar a usar as tecnologias e frameworks mais recentes de maneira mais rápida e eficiente, em vez de ficar preso a uma pilha ou framework mais antigo para todo o aplicativo.

Desvantagens de uma solução baseada em microsserviço

Uma solução baseada em microsserviço como essa também tem algumas desvantagens:

Aplicativo distribuído. A distribuição do aplicativo adiciona complexidade aos desenvolvedores quando eles estão projetando e criando os serviços. Por exemplo, os desenvolvedores devem implementar a comunicação entre serviços usando protocolos como HTTP ou AMQP, o que adiciona complexidade para teste e tratamento de exceções. Ele também adiciona latência ao sistema.

Complexidade da implantação. Um aplicativo que tem dezenas de tipos de microsserviços e precisa de alta escalabilidade (ele precisa ser capaz de criar muitas instâncias por serviço e balancear esses serviços em muitos hosts) significa um alto grau de complexidade de implantação para operações e gerenciamento de TI. Se você não estiver usando uma infraestrutura orientada a microsserviços (como um orquestrador e agendador), essa complexidade adicional poderá exigir muito mais esforços de desenvolvimento do que o próprio aplicativo de negócios.

Transações atômicas. Transações atômicas entre vários microsserviços geralmente não são possíveis. Os requisitos corporativos precisam adotar a consistência eventual entre vários microsserviços. Para obter mais informações, consulte os desafios do processamento de mensagens idempotente.

Aumento das necessidades globais de recursos (memória total, unidades e recursos de rede para todos os servidores ou hosts). Em muitos casos, quando você substitui um aplicativo monolítico por uma abordagem de microsserviços, a quantidade de recursos globais iniciais necessários para o novo aplicativo baseado em microsserviço será maior do que as necessidades de infraestrutura do aplicativo monolítico original. Essa abordagem ocorre porque o maior grau de granularidade e serviços distribuídos requer mais recursos globais. No entanto, dado o baixo custo dos recursos em geral e o benefício de ser capaz de escalar determinadas áreas do aplicativo em comparação com os custos de longo prazo ao evoluir aplicativos monolíticos, o aumento do uso de recursos geralmente é uma boa compensação para aplicativos grandes e de longo prazo.

Problemas com a comunicação direta de cliente para microsserviço. Quando o aplicativo é grande, com dezenas de microsserviços, há desafios e limitações se o aplicativo exigir comunicações diretas de cliente para microsserviço. Um problema é uma possível incompatibilidade entre as necessidades do cliente e as APIs expostas por cada um dos microsserviços. Em determinados casos, o aplicativo cliente pode precisar fazer muitas solicitações separadas para compor a interface do usuário, o que pode ser ineficiente pela Internet e seria impraticável em uma rede móvel. Portanto, as solicitações do aplicativo cliente para o sistema de back-end devem ser minimizadas.

Outro problema com comunicações diretas de cliente para microsserviço é que alguns microsserviços podem estar usando protocolos que não são amigáveis com a Web. Um serviço pode usar um protocolo binário, enquanto outro serviço pode usar mensagens AMQP. Esses protocolos não são amigáveis ao firewall e são mais usados internamente. Normalmente, um aplicativo deve usar protocolos como HTTP e WebSockets para comunicação fora do firewall.

Outra desvantagem com essa abordagem direta cliente a serviço é que isso dificulta a refatoração dos contratos para esses microsserviços. Com o tempo, os desenvolvedores podem querer alterar a forma como o sistema é particionado em serviços. Por exemplo, eles podem mesclar dois serviços ou dividir um serviço em dois ou mais serviços. No entanto, se os clientes se comunicarem diretamente com os serviços, a execução desse tipo de refatoração poderá interromper a compatibilidade com aplicativos cliente.

Conforme mencionado na seção de arquitetura, ao projetar e criar um aplicativo complexo com base em microsserviços, você pode considerar o uso de vários Gateways de API refinados em vez da abordagem mais simples de comunicação entre clientes e microsserviços.

Particionamento dos microsserviços. Por fim, não importa qual abordagem você adotar para sua arquitetura de microsserviço, outro desafio é decidir como particionar um aplicativo de ponta a ponta em vários microsserviços. Conforme observado na seção de arquitetura do guia, há várias técnicas e abordagens que você pode adotar. Basicamente, você precisa identificar as áreas do aplicativo que são dissociadas das outras áreas e que têm um número baixo de dependências difíceis. Em muitos casos, essa abordagem está alinhada ao particionamento de serviços por casos de uso. Por exemplo, em nosso aplicativo de loja eletrônico, temos um serviço de ordenação responsável por toda a lógica de negócios relacionada ao processo de pedido. Também temos o serviço de catálogo e o serviço de cesta que implementam outras funcionalidades. O ideal é que cada serviço tenha apenas um pequeno conjunto de responsabilidades. Essa abordagem é semelhante ao SRP (princípio de responsabilidade única) aplicado às classes, que afirma que uma classe deve ter apenas um motivo para mudar. Mas, nesse caso, trata-se de microsserviços, portanto, o escopo será maior que uma única classe. Acima de tudo, um microsserviço precisa ser autônomo, de ponta a ponta, incluindo a responsabilidade por suas próprias fontes de dados.

Arquitetura externa versus interna e padrões de design

A arquitetura externa é a arquitetura de microsserviço composta por vários serviços, seguindo os princípios descritos na seção de arquitetura deste guia. No entanto, dependendo da natureza de cada microsserviço e independentemente da arquitetura de microsserviço de alto nível escolhida, é comum e, às vezes, aconselhável ter arquiteturas internas diferentes, cada uma com base em padrões diferentes, para microsserviços diferentes. Os microsserviços podem até mesmo usar diferentes tecnologias e linguagens de programação. A Figura 6-2 ilustra essa diversidade.

Diagrama comparando padrões de arquitetura externa e interna.

Figura 6-2. Arquitetura e design externos versus internos

Por exemplo, em nosso exemplo eShopOnContainers , os microsserviços de catálogo, cesta e perfil de usuário são simples (basicamente, subsistemas CRUD). Portanto, sua arquitetura interna e design são simples. No entanto, você pode ter outros microsserviços, como o microsserviço de pedidos, que é mais complexo e lida com regras de negócios em constante evolução e de alta complexidade de domínio. Em casos como esses, talvez você queira implementar padrões mais avançados em um microsserviço específico, como os definidos com abordagens de DDD (design controlado por domínio), como estamos fazendo no microsserviço de ordenação do eShopOnContainers . (Examinaremos esses padrões de DDD na seção posteriormente que explica a implementação do microsserviço de ordenação eShopOnContainers .)

Outro motivo para uma tecnologia diferente por microsserviço pode ser a natureza de cada microsserviço. Por exemplo, talvez seja melhor usar uma linguagem de programação funcional como F#ou até mesmo uma linguagem como R se você estiver direcionando domínios de IA e machine learning, em vez de uma linguagem de programação mais orientada a objetos, como C#.

A questão é que cada microsserviço pode ter uma arquitetura interna diferente com base em diferentes padrões de design. Nem todos os microsserviços devem ser implementados usando padrões de DDD avançados, pois isso seria complicar demais. Da mesma forma, microsserviços complexos com lógica de negócios em constante mudança não devem ser implementados como componentes CRUD ou você pode acabar com código de baixa qualidade.

O novo mundo: vários padrões arquitetônicos e microsserviços poliglotas

Há muitos padrões de arquitetura usados por arquitetos e desenvolvedores de software. Veja a seguir alguns (combinando estilos de arquitetura e padrões de arquitetura):

Você também pode criar microsserviços com várias tecnologias e idiomas, como ASP.NET Principais APIs Web, NancyFx, ASP.NET Core SignalR (disponível com o .NET Core 2 ou posterior), F#, Node.js, Python, Java, C++, GoLang e muito mais.

O ponto importante é que nenhum padrão ou estilo de arquitetura específico, nem qualquer tecnologia específica, é adequado para todas as situações. A Figura 6-3 mostra algumas abordagens e tecnologias (embora não em nenhuma ordem específica) que poderiam ser usadas em microsserviços diferentes.

Diagrama mostrando 12 microsserviços complexos em uma arquitetura de mundo poliglota.

Figura 6-3. Padrões de arquiteturas múltiplas e o mundo dos microsserviços poliglotas

Padrão multi-arquitetural e microsserviços poliglotas significa que você pode misturar e combinar linguagens e tecnologias conforme as necessidades de cada microsserviço e ainda assim fazê-los se comunicar entre si. Conforme mostrado na Figura 6-3, em aplicativos compostos por muitos microsserviços (contextos limitados na terminologia de design controlada pelo domínio ou simplesmente "subsistemas" como microsserviços autônomos), você pode implementar cada microsserviço de uma maneira diferente. Cada um pode ter um padrão de arquitetura diferente e usar diferentes idiomas e bancos de dados, dependendo da natureza do aplicativo, dos requisitos de negócios e das prioridades. Em alguns casos, os microsserviços podem ser semelhantes. Mas isso geralmente não é o caso, porque os limites de contexto e os requisitos de cada subsistema geralmente são diferentes.

Por exemplo, para um aplicativo de manutenção CRUD simples, talvez não faça sentido projetar e implementar padrões DDD. Mas para seu domínio principal ou negócio principal, talvez seja necessário aplicar padrões mais avançados para lidar com a complexidade dos negócios com regras de negócios em constante mudança.

Especialmente quando você lida com aplicativos grandes compostos por vários subsistemas, não deve aplicar uma única arquitetura de nível superior com base em um único padrão de arquitetura. Por exemplo, o CQRS não deve ser aplicado como uma arquitetura de nível superior para um aplicativo inteiro, mas pode ser útil para um conjunto específico de serviços.

Não há solução mágica ou um padrão de arquitetura certo para todos os casos. Você não pode ter "um padrão de arquitetura para governar todos eles". Dependendo das prioridades de cada microsserviço, você deve escolher uma abordagem diferente para cada um, conforme explicado nas seções a seguir.