Comunicação inter-serviços — síncrono vs assíncrono

TL;DR

Quando você quebra um monolito em serviços, a chamada de método que antes acontecia dentro do processo vira uma chamada pela rede. E a rede falha, atrasa e some. A pergunta central de toda arquitetura de microsserviços é: esse serviço deveria esperar a resposta do outro (síncrono) ou apenas avisar que algo aconteceu e seguir em frente (assíncrono)?

  • Síncrono (REST, @HttpExchange/Feign, gRPC): request/response, simples de raciocinar, mas cria acoplamento temporal — se o serviço de baixo cai, o de cima cai junto (falha em cascata).
  • Assíncrono (eventos/mensageria): o emissor publica e não espera; desacopla, é resiliente, mas paga o preço da consistência eventual. Não existe “o melhor” — existe o eixo de decisão. Você escolhe por caso de uso.

O que é

Num monolito, quando o módulo de pedidos precisa cobrar um cartão, ele chama um método: pagamentoService.cobrar(...). A chamada é local, instantânea, e ou retorna ou lança exceção. Não há rede no meio.

Quando você separa “pedidos” e “pagamentos” em dois serviços independentes, essa chamada de método deixa de existir. Para um serviço pedir algo ao outro, agora ele precisa atravessar a rede — e a rede é um meio hostil: tem latência, pode estar congestionada, o serviço do outro lado pode estar reiniciando, ou simplesmente fora do ar.

A metáfora de Fowler: smart endpoints and dumb pipes

Martin Fowler descreve o estilo de comunicação de microsserviços como “smart endpoints and dumb pipes” (pontas inteligentes, canos burros): a inteligência mora nos serviços, não na infraestrutura de comunicação. O “cano” — seja HTTP ou um broker de mensagens — só transporta. Isso se opõe aos Enterprise Service Bus (ESB) do passado, que enfiavam regra de negócio na camada de transporte.

A Comunicação inter-serviços é, então, o conjunto de decisões sobre como esses serviços conversam pela rede. E a primeira bifurcação — a mais importante — é entre síncrono e assíncrono.

Por que importa

Essa escolha não é um detalhe técnico de implementação. Ela define o comportamento de falha do seu sistema inteiro.

Pense assim: se você liga para alguém e fica no telefone esperando a resposta, vocês dois ficam presos àquela ligação. Se a outra pessoa não atende, você fica parado, segurando o fone. Isso é síncrono. Agora, se você manda uma mensagem e volta a fazer outras coisas, vocês estão desacoplados no tempo — a pessoa responde quando puder. Isso é assíncrono.

Fowler é direto sobre o risco do síncrono: “any time you have a number of synchronous calls between services you will encounter the multiplicative effect of downtime” — toda vez que você encadeia chamadas síncronas, as indisponibilidades se multiplicam. Três serviços com 99,9% de uptime cada, encadeados síncronamente, não te dão 99,9% — te dão algo pior, porque qualquer um deles derrubando derruba a cadeia toda.

Saber escolher o eixo certo é o que separa um sistema distribuído resiliente de uma casa de cartas que cai quando um único serviço espirra.

Como funciona

Síncrono — request/response e acoplamento temporal

No modelo síncrono, o serviço A faz uma requisição ao serviço B e bloqueia esperando a resposta antes de continuar. É o velho request/response da web.

Em Java/Spring, isso aparece como:

  • REST sobre HTTP com um cliente declarativo: @HttpExchange (HTTP Interface, nativo do Spring 6) ou OpenFeign — você descreve a chamada como uma interface e o framework gera o cliente.
  • gRPC — RPC binário sobre HTTP/2, mais rápido e fortemente tipado que REST. (Aprofundado no Galho 14 — Mensageria; ver gRPC. Aqui só importa que gRPC, apesar do nome “mensageria”, é tipicamente um modelo síncrono request/response.)

A vantagem é a simplicidade de raciocínio: você chama, espera, e tem a resposta na mão para usar na linha seguinte. O fluxo é linear, fácil de debugar, fácil de testar.

A armadilha é o acoplamento temporal (temporal coupling): A e B precisam estar vivos ao mesmo tempo para a operação funcionar. Se B está fora do ar, A não tem o que fazer a não ser falhar (ou esperar, o que muitas vezes é pior). E quando A falha porque B falhou, e C dependia de A… você tem uma falha em cascata (cascading failure).

Assíncrono — eventos, desacoplamento e consistência eventual

No modelo assíncrono, o serviço A não chama o serviço B. Em vez disso, A publica um evento (“Pedido foi criado”) num broker de mensagens (Kafka, RabbitMQ) e segue em frente imediatamente, sem esperar ninguém. Quem se interessar pelo evento — B, C, ou um serviço que nem existe ainda — consome quando puder.

A inversão fundamental

No síncrono, A sabe quem é B e manda B fazer algo (“cobre esse cartão, agora, e me diga se deu certo”). No assíncrono, A apenas anuncia um fato (“este pedido foi criado”) e não sabe nem se importa quem vai reagir. O conhecimento sobre “quem faz o quê” sai do emissor.

A vantagem é o desacoplamento temporal: B pode estar fora do ar quando o evento é publicado. A mensagem fica no broker, esperando. Quando B volta, ele processa a fila acumulada. A nunca soube que B estava offline, e nunca parou por isso. Fowler resume a receita: “make your calls asynchronous” como forma de gerenciar o downtime.

O preço é a consistência eventual (eventual consistency): logo depois que A publica “Pedido criado”, o pagamento ainda não foi processado. Existe uma janela de tempo em que o sistema está num estado “intermediário”, e seu código (e seu produto) precisam tolerar isso. “Eventualmente” tudo se acerta — mas não instantaneamente.

Fronteira de galho

Como coordenar uma transação de negócio que atravessa vários serviços assíncronos (ex.: criar pedido → reservar estoque → cobrar pagamento, com rollback se algo falha) é o padrão Saga, e isso é assunto do Galho 14 — Mensageria. Esta nota só estabelece o eixo de decisão síncrono/assíncrono. O como do event-driven (Saga, outbox, event sourcing) não é re-explicado aqui — ver Saga.

O eixo de decisão — quando cada um

Não escolha por modismo. Escolha pela natureza da operação:

Use síncrono quando…Use assíncrono quando…
Você precisa da resposta agora para continuar (ex.: validar saldo antes de confirmar a compra).A operação pode acontecer “em segundo plano” (ex.: enviar e-mail de confirmação, atualizar relatório).
A consistência forte é obrigatória no momento da resposta.Consistência eventual é aceitável pelo negócio.
O fluxo é uma consulta (leitura) que retorna dados.O fluxo é uma notificação de fato (“isto aconteceu”).
Há poucos saltos e o acoplamento temporal é tolerável.Você quer desacoplar, resistir a quedas e escalar consumidores independentemente.

Regra de bolso: leitura/consulta tende a síncrono; reação a eventos de negócio tende a assíncrono. E a maioria dos sistemas reais é híbrida — usa os dois, cada um onde faz sentido.

Na prática

O mesmo cenário — order-service precisa que payment-service cobre um pagamento — resolvido nos dois eixos:

SÍNCRONO (request/response — acoplamento temporal)
 
  order-service                          payment-service
       |                                       |
       |  POST /payments  (HTTP/gRPC)          |
       |-------------------------------------->|
       |                                       | processa...
       |          200 OK { status: paid }      |
       |<--------------------------------------|
       |                                       |
   (BLOQUEADO esperando)            se payment-service cai aqui,
                                    order-service FALHA junto
                                    -> falha em cascata
 
 
ASSÍNCRONO (evento — desacoplamento temporal)
 
  order-service        message broker         payment-service
       |                    |                       |
       | publish            |                       |
       | "OrderCreated" --->|                       |
       |  (segue em frente) |                       |
       |                    |---- entrega --------->| processa...
       |                    |                       |
       |                    |<-- "PaymentDone" -----|
       |                    |                       |
  order-service NUNCA       se payment-service está fora,
  ficou bloqueado;          a mensagem ESPERA na fila e é
  reage ao evento de        entregue quando ele voltar
  volta quando chega        -> sem cascata

No síncrono, os dois serviços compartilham o mesmo instante. No assíncrono, o broker (o “cano burro”) absorve a diferença de tempo entre quem produz e quem consome.

Armadilhas

(1) Fazer tudo síncrono “porque é mais simples”

É a armadilha mais comum, e a mais cara. REST síncrono é fácil de começar, então times saem encadeando chamada após chamada: o serviço A chama B, que chama C, que chama D — tudo bloqueante. Funciona lindamente no ambiente de dev, com tudo no ar.

Em produção, isso vira o efeito multiplicativo do downtime que Fowler alerta. Cada salto adiciona latência (você espera A+B+C+D somados) e cada serviço vira um ponto único de falha para toda a corrente. D piscou? A operação inteira morre. Você construiu um monolito distribuído — com todo o acoplamento do monolito mais a fragilidade da rede. O acoplamento temporal te pega de surpresa só quando algo cai.

(2) Fazer assíncrono onde uma chamada simples bastava

O oposto também dói. Eventos e mensageria são poderosos, mas trazem complexidade real: broker para operar, consistência eventual para o produto tolerar, ordenação de mensagens, idempotência, rastreamento distribuído de um fluxo que não é mais linear. Debugar “por que esse pedido não foi cobrado” num fluxo de 5 eventos é muito mais difícil do que ler um stacktrace de uma chamada síncrona.

Se a operação é uma consulta que precisa da resposta imediata — “qual o saldo deste cliente?” — transformar isso em evento é over-engineering. Você paga toda a complexidade do assíncrono para resolver um problema que uma chamada request/response resolveria em uma linha. Async não é “mais moderno”; é uma troca. Use quando o desacoplamento vale o custo, não por reflexo.

(3) Ignorar que síncrono precisa de rede de proteção

Mesmo quando síncrono é a escolha certa, ele não pode ser “ingênuo”. Uma chamada HTTP sem timeout fica pendurada esperando para sempre quando o outro lado trava — e segura recursos (threads, conexões) até esgotar. Sem retry, circuit breaker e fallback, uma chamada síncrona é uma cascata esperando para acontecer. (Esses padrões de resiliência são o foco do resto deste Galho 16 — ver Padrões de falha distribuída.)

Em entrevista

Frase pronta (inglês)

“The core decision in inter-service communication is synchronous versus asynchronous. Synchronous request-response — REST, HTTP interfaces, or gRPC — is simple to reason about, but it creates temporal coupling: the caller and the callee must both be alive at the same time, and chained synchronous calls multiply downtime, leading to cascading failures. Asynchronous, event-driven communication decouples services in time through a message broker, which makes the system far more resilient, but the trade-off is eventual consistency. There’s no universally correct answer — I pick per use case: synchronous when I need the response immediately to proceed, asynchronous when I’m reacting to a business fact and can tolerate eventual consistency. Most real systems are hybrid, and even synchronous calls need timeouts, retries, and circuit breakers to avoid becoming cascading failures.”

Vocabulário

  • síncronosynchronous
  • assíncronoasynchronous
  • acoplamento temporaltemporal coupling
  • orientado a eventosevent-driven
  • falha em cascatacascading failure
  • requisição-respostarequest-response
  • consistência eventualeventual consistency
  • desacoplamentodecoupling / loose coupling
  • corretor de mensagens / brokermessage broker

Veja também

Referências

  • Lewis, James; Fowler, Martin. Microservices — martinfowler.com. https://martinfowler.com/articles/microservices.html — seções Smart endpoints and dumb pipes, Decentralized Governance, Decentralized Data Management e o alerta “Synchronous calls considered harmful” / efeito multiplicativo do downtime.