Compartilhar via


Comunicação em uma arquitetura de microsserviço

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.

Em um aplicativo monolítico, em um único processo em execução, os componentes se invocam mutuamente usando chamadas de métodos ou funções da própria linguagem. Elas podem ser fortemente acopladas se você estiver criando objetos com código (por exemplo), new ClassName()ou pode ser invocada de forma desacoplada se você estiver usando a Injeção de Dependência fazendo referência a abstrações em vez de instâncias de objeto concretas. De qualquer forma, os objetos estão em execução no mesmo processo. O maior desafio ao mudar de um aplicativo monolítico para um aplicativo baseado em microsserviços está na alteração do mecanismo de comunicação. Uma conversão direta das chamadas de método internas em processo em chamadas RPC para os serviços causará uma comunicação muito intensa e ineficiente que não terá um bom desempenho em ambientes distribuídos. Os desafios de projetar o sistema distribuído corretamente já são suficientemente conhecidos de que há até mesmo um cânone conhecido como Fallacies da computação distribuída que lista suposições que os desenvolvedores costumam fazer ao mover de designs monolíticos para distribuídos.

Não há uma solução, mas várias. Uma solução envolve isolar os microsserviços de negócios o máximo possível. Em seguida, você usa a comunicação assíncrona entre os microsserviços internos e substitui a comunicação detalhada, que é típica na comunicação intraprocesso entre objetos, por uma comunicação de maior granularidade. Você pode fazer isso agrupando chamadas e retornando dados que agregam os resultados de várias chamadas internas ao cliente.

Um aplicativo baseado em microsserviços é um sistema distribuído em execução em vários processos ou serviços, geralmente até mesmo em vários servidores ou hosts. Cada instância de serviço normalmente é um processo. Portanto, os serviços devem interagir usando um protocolo de comunicação entre processos, como HTTP, AMQP ou um protocolo binário como TCP, dependendo da natureza de cada serviço.

A comunidade de microsserviços promove a filosofia de "pontos de extremidade inteligentes e pipes idiotas". Este slogan incentiva um design o mais desassociado possível entre microsserviços e o mais coeso possível em um único microsserviço. Conforme explicado anteriormente, cada microsserviço possui seus próprios dados e sua própria lógica de domínio. Mas os microsserviços que compõem um aplicativo de ponta a ponta geralmente são simplesmente coreografados usando comunicações REST em vez de protocolos complexos, como WS-* e comunicações flexíveis orientadas a eventos em vez de orquestradores de processo de negócios centralizados.

Os dois protocolos comumente usados são solicitação/resposta HTTP com APIs de recurso (principalmente ao consultar dados) e mensagens assíncronas leves ao transmitir atualizações entre múltiplos microsserviços. Elas são explicadas com mais detalhes nas seções a seguir.

Tipos de comunicação

O cliente e os serviços podem se comunicar por meio de vários tipos diferentes de comunicação, cada um direcionado a um cenário e metas diferentes. Inicialmente, esses tipos de comunicações podem ser classificados em dois eixos.

O primeiro eixo define se o protocolo é síncrono ou assíncrono:

  • Protocolo síncrono. HTTP é um protocolo síncrono. O cliente envia uma solicitação e aguarda uma resposta do serviço. Isso é independente da execução do código do cliente que pode ser síncrona (o thread é bloqueado) ou assíncrona (o thread não é bloqueado e a resposta acabará alcançando um retorno de chamada). O ponto importante aqui é que o protocolo (HTTP/HTTPS) é síncrono e o código do cliente só pode continuar sua tarefa quando receber a resposta do servidor HTTP.

  • Protocolo assíncrono. Outros protocolos como AMQP (um protocolo com suporte de muitos sistemas operacionais e ambientes de nuvem) usam mensagens assíncronas. O remetente de código ou mensagem do cliente geralmente não aguarda uma resposta. Ele apenas envia a mensagem como ao enviar uma mensagem para uma fila do RabbitMQ ou para qualquer outro agente de mensagens.

O segundo eixo define se a comunicação tem um único receptor ou vários receptores:

  • Receptor único. Cada solicitação deve ser processada por exatamente um receptor ou serviço. Um exemplo dessa comunicação é o padrão Command.

  • Vários receptores. Cada solicitação pode ser processada por zero ou mais receptores. Esse tipo de comunicação deve ser assíncrono. Um exemplo é o mecanismo de publicação/assinatura usado em padrões como a arquitetura controlada por eventos. Ele se baseia em um agente de mensagem ou em uma interface de barramento de evento ao propagar atualizações de dados entre vários microsserviços por meio de eventos. Ele geralmente é implementado por meio de um barramento de serviço ou artefato semelhante, como o Barramento de Serviço do Azure, usando tópicos e assinaturas.

Um aplicativo baseado em microsserviço geralmente usará uma combinação desses estilos de comunicação. O tipo mais comum é a comunicação de receptor único com um protocolo síncrono como HTTP/HTTPS ao invocar um serviço HTTP de API Web regular. Os microsserviços também normalmente usam protocolos de mensagens para comunicação assíncrona entre microsserviços.

Esses eixos são bons de se saber para que você tenha clareza sobre os possíveis mecanismos de comunicação, mas eles não são as preocupações importantes ao criar microsserviços. Nem a natureza assíncrona da execução do thread do cliente nem a natureza assíncrona do protocolo selecionado são os pontos importantes ao integrar microsserviços. O importante é poder integrar seus microsserviços de forma assíncrona, mantendo a independência dos microsserviços, conforme explicado na seção a seguir.

A integração assíncrona de microsserviço impõe a autonomia do microsserviço

Conforme mencionado, o ponto importante ao criar um aplicativo baseado em microsserviços é a maneira como você integra seus microsserviços. Idealmente, você deve tentar minimizar a comunicação entre os microsserviços internos. Quanto menos comunicações entre microsserviços, melhor. Mas, em muitos casos, você precisará integrar de alguma forma os microsserviços. Quando você precisa fazer isso, a regra crítica aqui é que a comunicação entre os microsserviços deve ser assíncrona. Isso não significa que você tenha que usar um protocolo específico (por exemplo, mensagens assíncronas versus HTTP síncrono). Isso significa apenas que a comunicação entre microsserviços deve ser feita apenas propagando dados de forma assíncrona, mas tente não depender de outros microsserviços internos como parte da operação de solicitação/resposta HTTP do serviço inicial.

Se possível, nunca dependerá da comunicação síncrona (solicitação/resposta) entre vários microsserviços, nem mesmo para consultas. A meta de cada microsserviço é ser autônomo e disponível para o consumidor, mesmo que os outros serviços que fazem parte do aplicativo de ponta a ponta estejam fora do ar ou sejam problemáticos. Se você acha que precisa fazer uma chamada de um microsserviço para outros microsserviços (como executar uma solicitação HTTP para uma consulta de dados) para poder fornecer uma resposta a um aplicativo cliente, você terá uma arquitetura que não será resiliente quando alguns microsserviços falharem.

Além disso, ter dependências HTTP entre microsserviços, como ao criar longos ciclos de solicitação/resposta com cadeias de solicitação HTTP, conforme mostrado na primeira parte da Figura 4-15, não apenas torna seus microsserviços não autônomos, mas também seu desempenho é afetado assim que um dos serviços dessa cadeia não está tendo um bom desempenho.

Quanto mais você adicionar dependências síncronas entre microsserviços, como solicitações de consulta, pior será o tempo de resposta geral para os aplicativos cliente.

Diagrama mostrando três tipos de comunicações entre microsserviços.

Figura 4-15. Antipadrões e padrões na comunicação entre microsserviços

Conforme mostrado no diagrama acima, na comunicação síncrona, uma "cadeia" de solicitações é criada entre microsserviços enquanto atende à solicitação do cliente. Esse é um antipadrão. Em microsserviços de comunicação assíncrona, use mensagens assíncronas ou sondagem http para se comunicar com outros microsserviços, mas a solicitação do cliente é atendida imediatamente.

Se o microsserviço precisar gerar uma ação adicional em outro microsserviço, se possível, não execute essa ação de forma síncrona e como parte da operação de resposta e solicitação de microsserviço original. Em vez disso, faça isso de forma assíncrona (usando mensagens assíncronas ou eventos de integração, filas etc.). Mas, tanto quanto possível, não invoque a ação de forma síncrona como parte da operação de solicitação e resposta síncrona original.

E, por fim (e é aqui que a maioria dos problemas surgem ao criar microsserviços), se o microsserviço inicial precisar de dados que pertencem originalmente a outros microsserviços, não dependa de fazer solicitações síncronas para esses dados. Em vez disso, replique ou propague esses dados (apenas os atributos necessários) no banco de dados do serviço inicial usando a consistência eventual (normalmente usando eventos de integração, conforme explicado nas próximas seções).

Conforme observado anteriormente na Identificação dos limites do modelo de domínio para cada seção de microsserviço , duplicar alguns dados em vários microsserviços não é um design incorreto, pelo contrário, ao fazer isso, você pode traduzir os dados para o idioma específico ou termos desse domínio adicional ou contexto limitado. Por exemplo, no aplicativo eShopOnContainers , você tem um microsserviço chamado identity-api responsável pela maioria dos dados do usuário com uma entidade chamada User. No entanto, quando for necessário armazenar os dados do usuário no microsserviço Ordering, armazene-os como uma entidade diferente nomeada Buyer. A Buyer entidade compartilha a mesma identidade com a entidade original User , mas pode ter apenas os poucos atributos necessários para o Ordering domínio e não todo o perfil de usuário.

Você pode usar qualquer protocolo para se comunicar e propagar dados de forma assíncrona entre microsserviços para ter consistência eventual. Conforme mencionado, você pode usar eventos de integração usando um barramento de eventos ou agente de mensagens ou até mesmo usar HTTP sondando os outros serviços. Não importa. A regra importante é não criar dependências síncronas entre seus microsserviços.

As seções a seguir explicam os vários estilos de comunicação que você pode considerar usando em um aplicativo baseado em microsserviço.

Estilos de comunicação

Há muitos protocolos e opções que você pode usar para comunicação, dependendo do tipo de comunicação que você deseja usar. Se você estiver usando um mecanismo de comunicação síncrono baseado em solicitação/resposta, protocolos como abordagens HTTP e REST serão os mais comuns, especialmente se você estiver publicando seus serviços fora do host do Docker ou do cluster de microsserviços. Se você estiver se comunicando entre os serviços internamente (dentro de seu host do Docker ou cluster de microsserviços), talvez você também queira usar mecanismos de comunicação de formato binário (como WCF usando TCP e formato binário). Como alternativa, você pode usar mecanismos de comunicação assíncronos baseados em mensagens, como AMQP.

Há também vários formatos de mensagem, como JSON ou XML, ou até mesmo formatos binários, que podem ser mais eficientes. Se o formato binário escolhido não for um padrão, provavelmente não é uma boa ideia publicar publicamente seus serviços usando esse formato. Você pode usar um formato não padrão para comunicação interna entre seus microsserviços. Você pode fazer isso ao se comunicar entre microsserviços em seu host do Docker ou cluster de microsserviços (por exemplo, orquestradores do Docker) ou para aplicativos cliente proprietários que conversam com os microsserviços.

Comunicação de solicitação/resposta com HTTP e REST

Quando um cliente usa a comunicação de solicitação/resposta, ele envia uma solicitação para um serviço e, em seguida, o serviço processa a solicitação e envia uma resposta de volta. A comunicação de solicitação/resposta é especialmente adequada para consultar dados de uma interface do usuário em tempo real (uma interface do usuário dinâmica) de aplicativos cliente. Portanto, em uma arquitetura de microsserviço, você provavelmente usará esse mecanismo de comunicação para a maioria das consultas, conforme mostrado na Figura 4-16.

Diagrama mostrando mensagens de solicitação/resposta para consultas e atualizações dinâmicas.

Figura 4-16. Usando a comunicação de solicitação/resposta HTTP (síncrona ou assíncrona)

Quando um cliente usa a comunicação de solicitação/resposta, ele pressupõe que a resposta chegará em pouco tempo, normalmente menos de um segundo ou alguns segundos no máximo. Para respostas atrasadas, você precisa implementar a comunicação assíncrona com base em padrões de mensagens e tecnologias de mensagens, que é uma abordagem diferente que explicamos na próxima seção.

Um estilo de arquitetura popular para comunicação de solicitação/resposta é REST. Essa abordagem se baseia e está firmemente acoplada ao protocolo HTTP , adotando verbos HTTP como GET, POST e PUT. REST é a abordagem de comunicação arquitetônica mais usada ao criar serviços. Você pode implementar serviços REST ao desenvolver ASP.NET principais serviços de API Web.

Há um valor adicional ao usar os serviços REST HTTP como linguagem de definição de interface. Por exemplo, ao usar metadados do Swagger para descrever sua API de serviço, você poderá usar ferramentas que geram stubs de cliente para descobrir e consumir seus serviços diretamente.

Recursos adicionais

Comunicação por push e em tempo real com base em HTTP

Outra possibilidade (geralmente para fins diferentes do REST) é uma comunicação em tempo real e um para muitos com estruturas de nível superior, como ASP.NET SignalR e protocolos como WebSockets.

Como mostra a Figura 4-17, a comunicação HTTP em tempo real significa que você pode ter o código do servidor enviando conteúdo por push para clientes conectados à medida que os dados ficam disponíveis, em vez de fazer com que o servidor aguarde um cliente solicitar novos dados.

Diagrama mostrando mensagens push e em tempo real com base no SignalR.

Figura 4-17. Comunicação assíncrona de mensagem em tempo real de um para muitos

O SignalR é uma boa maneira de obter comunicação em tempo real para enviar conteúdo por push para os clientes de um servidor de back-end. Como a comunicação está em tempo real, os aplicativos cliente mostram as alterações quase instantaneamente. Isso geralmente é tratado por um protocolo como WebSockets, usando muitas conexões WebSockets (uma por cliente). Um exemplo típico é quando um serviço comunica uma alteração na pontuação de um jogo esportivo para muitos aplicativos Web cliente simultaneamente.