Partilhar via


Comunicação em uma arquitetura de microsserviços

Gorjeta

Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Em um aplicativo monolítico em execução em um único processo, os componentes invocam uns aos outros usando o método de nível de linguagem ou chamadas de função. Eles podem ser fortemente acoplados se você estiver criando objetos com código (por exemplo, new ClassName()), ou podem ser invocados de forma dissociada 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 sendo executados dentro do mesmo processo. O maior desafio ao mudar de um aplicativo monolítico para um aplicativo baseado em microsserviços reside na alteração do mecanismo de comunicação. Uma conversão direta de chamadas de método em processo em chamadas RPC para serviços causará uma comunicação tagarela e não eficiente que não terá um bom desempenho em ambientes distribuídos. Os desafios de projetar o sistema distribuído corretamente são bem conhecidos o suficiente para que haja até mesmo um cânone conhecido como Falácias da computação distribuída que lista suposições que os desenvolvedores geralmente fazem ao passar de projetos monolíticos para projetos distribuídos.

Não há uma solução, mas várias. Uma solução envolve isolar ao máximo os microsserviços empresariais. Em seguida, você usa a comunicação assíncrona entre os microsserviços internos e substitui a comunicação refinada típica na comunicação intraprocesso entre objetos por uma comunicação de grão mais grosso. Você pode fazer isso agrupando chamadas e retornando dados que agregam os resultados de várias chamadas internas para o cliente.

Um aplicativo baseado em microsserviços é um sistema distribuído executado 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 "terminais inteligentes e tubos burros". Este slogan incentiva um design o mais dissociado possível entre microsserviços e o mais coeso possível dentro de um único microsserviço. Como 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 centralizados de processos de negócios.

Os dois protocolos comumente usados são solicitação/resposta HTTP com APIs de recursos (ao consultar acima de tudo) e mensagens assíncronas leves ao comunicar atualizações em vários microsserviços. Estes são explicados em mais pormenor nas secções seguintes.

Tipos de comunicação

Cliente e serviços podem se comunicar através de muitos tipos diferentes de comunicação, cada um visando um cenário e objetivos diferentes. Inicialmente, esses tipos de comunicação 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 um pedido e aguarda uma resposta do serviço. Isso é independente da execução do código do cliente que pode ser síncrona (o thread está bloqueado) ou assíncrona (o thread não está bloqueado e a resposta chegará a um retorno de chamada eventualmente). O ponto importante aqui é que o protocolo (HTTP/HTTPS) é síncrono e o código do cliente só pode continuar sua tarefa quando recebe a resposta do servidor HTTP.

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

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

  • Recetor único. Cada pedido deve ser processado exatamente por um destinatário ou serviço. Um exemplo dessa comunicação é o padrão Command.

  • Vários recetores. Cada pedido pode ser processado por zero a vários destinatários. Este tipo de comunicação deve ser assíncrona. Um exemplo é o mecanismo de publicação/assinatura usado em padrões como arquitetura orientada a eventos. Isso se baseia em uma interface de barramento de eventos ou agente de mensagens ao propagar atualizações de dados entre vários microsserviços por meio de eventos; 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 recetor único com um protocolo síncrono como HTTP/HTTPS ao invocar um serviço HTTP de API da 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 conhecer para que você tenha clareza sobre os possíveis mecanismos de comunicação, mas não são as preocupações importantes ao construir 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ços reforça a autonomia do microsserviço

Como 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ê terá que 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 dependa de comunicação síncrona (solicitação/resposta) entre vários microsserviços, nem mesmo para consultas. O objetivo de cada microsserviço é ser autônomo e estar disponível para o consumidor cliente, mesmo que os outros serviços que fazem parte do aplicativo de ponta a ponta estejam inativos ou não estejam íntegros. 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ê tem 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, como 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 nessa cadeia não está funcionando bem.

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.

Diagram showing three types of communications across microservices.

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

Como mostrado no diagrama acima, na comunicação síncrona uma "cadeia" de solicitações é criada entre microsserviços enquanto atende a solicitação do cliente. Este é um anti-padrão. Na comunicação assíncrona, os microsserviços usam 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 solicitação e resposta do microsserviço original. Em vez disso, faça-o 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, finalmente, (e é aqui que a maioria dos problemas surgem ao criar microsserviços), se o seu microsserviço inicial precisar de dados que são originalmente pertencentes a outros microsserviços, não confie em 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 consistência eventual (normalmente usando eventos de integração, conforme explicado nas próximas seções).

Como observado anteriormente na seção Identificando limites de modelo de domínio para cada 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 ou termos específicos desse domínio adicional ou contexto limitado. Por exemplo, no aplicativo eShopOnContainers, você tem um microsserviço chamado identity-api que é responsável pela maioria dos dados do usuário com uma entidade chamada User. No entanto, quando você precisa armazenar dados sobre o usuário no microsserviço, armazena-os Ordering como uma entidade diferente chamada 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 comunicar e propagar dados de forma assíncrona entre microsserviços, a fim de ter consistência eventual. Como mencionado, você pode usar eventos de integração usando um barramento de eventos ou agente de mensagens ou até mesmo usar HTTP pesquisando 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 usar 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 serviços internamente (dentro do host do Docker ou do cluster de microsserviços), talvez 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.

Existem também vários formatos de mensagem como JSON ou XML, ou 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 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 de volta uma resposta. A comunicação de solicitação/resposta é especialmente adequada para consultar dados para uma interface do usuário em tempo real (uma interface de usuário ao vivo) de aplicativos cliente. Portanto, em uma arquitetura de microsserviço, você provavelmente usará esse mecanismo de comunicação para a maioria das consultas, como mostra a Figura 4-16.

Diagram showing request/response comms for live queries and updates.

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

Quando um cliente usa a comunicação de solicitação/resposta, ele assume que a resposta chegará em um curto espaço de tempo, geralmente menos de um segundo, ou alguns segundos no máximo. Para respostas atrasadas, você precisa implementar 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 arquitetônico popular para comunicação de solicitação/resposta é o REST. Essa abordagem é baseada e fortemente acoplada ao protocolo HTTP , adotando verbos HTTP como GET, POST e PUT. REST é a abordagem de comunicação arquitetônica mais comumente usada ao criar serviços. Você pode implementar serviços REST ao desenvolver ASP.NET serviços de API Web principal.

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

Recursos adicionais

Push e comunicação 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 para clientes conectados à medida que os dados ficam disponíveis, em vez de fazer com que o servidor aguarde que um cliente solicite novos dados.

Diagram showing push and real-time comms based on SignalR.

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

O SignalR é uma boa maneira de obter comunicação em tempo real para enviar conteúdo para os clientes a partir de um servidor back-end. Como a comunicação é em tempo real, os aplicativos cliente mostram as mudanças 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 de esportes para muitos aplicativos Web clientes simultaneamente.