DEV Community

Cover image for Comunicação de microserviços em arquiteturas Spring Boot: Chamadas síncronas, Eventos e Integração de Inteligência Artificial com Spring AI
Wallace Maia
Wallace Maia

Posted on

Comunicação de microserviços em arquiteturas Spring Boot: Chamadas síncronas, Eventos e Integração de Inteligência Artificial com Spring AI

Resumo
Este artigo analisa estratégias de comunicação entre serviços Spring Boot, discutindo critérios para a escolha entre chamadas síncronas e mecanismos orientados a eventos. Abordam-se ainda formas de integrar serviços de inteligência artificial (IA) sem acoplá-los ao restante da aplicação. São examinados Spring Cloud OpenFeign, Service Discovery, Spring Cloud LoadBalancer, webhooks, mensageria e mecanismos de resiliência, com exemplos derivados de uma aplicação SaaS em desenvolvimento.


1. Introdução

À medida que uma aplicação cresce e suas responsabilidades são separadas, torna-se necessário definir como os serviços se comunicarão. A criação de projetos Spring Boot independentes é relativamente simples. A dificuldade surge quando um serviço depende de dados ou regras pertencentes a outro, por exemplo ao consultar informações de usuários, executar uma regra de negócio ou acessar uma API externa. Surge também a necessidade de processar tarefas demoradas sem manter uma requisição HTTP aberta durante todo o processamento.

Nesse contexto, a comunicação entre serviços deixa de ser um detalhe de implementação e passa a ser uma decisão arquitetural. Este trabalho discute o tema com ênfase em Spring Cloud OpenFeign, Service Discovery, Spring Cloud LoadBalancer, chamadas HTTP síncronas, webhooks, eventos e processamento assíncrono. Apresenta-se também sua aplicação em um SaaS real, no qual uma camada de IA é isolada do backend principal.

2. O desafio da localização de serviços

Uma arquitetura monolítica pode ser representada de forma simples. Com a separação de responsabilidades, a API principal passa a depender de múltiplos serviços, como autenticação, IA, pagamentos e notificações. Surge então a questão central: como a API principal localiza e invoca esses serviços?

A alternativa mais simples é registrar endereços fixos no código:

http://localhost:8082
http://10.0.4.27:8082
Enter fullscreen mode Exit fullscreen mode

Essa abordagem funciona até que ocorra alguma mudança de infraestrutura, como a alteração da instância, a execução de múltiplas réplicas, a migração de ambiente ou a adoção de Docker ou Kubernetes. Nesses casos, o endereço físico embutido no código torna-se uma fonte de acoplamento e de manutenção onerosa. O Service Discovery foi concebido para solucionar esse problema.

3. Service Discovery

No Service Discovery, o consumidor deixa de perguntar "onde está o serviço?" e passa a indicar "qual serviço é necessário?". No Spring Cloud, isso se expressa da seguinte forma:

@FeignClient(name = "ai-service")
Enter fullscreen mode Exit fullscreen mode

O código torna-se independente do endereço concreto da instância (localhost:8082, 10.0.2.15:8082 ou ai-service:8082), que é resolvido pela infraestrutura de descoberta. Um exemplo clássico no ecossistema Spring é o Netflix Eureka, que mantém um catálogo dos serviços registrados:

ai-service
    ├── 10.0.1.20:8082
    └── 10.0.1.21:8082

payment-service
    └── 10.0.1.30:8083
Enter fullscreen mode Exit fullscreen mode

A aplicação conhece apenas o nome lógico do serviço, que passa a representar sua identidade.

4. Spring Cloud OpenFeign

O Spring Cloud OpenFeign permite representar chamadas HTTP como interfaces Java. Sem esse recurso, é necessário gerenciar manualmente URL, cabeçalhos, serialização, desserialização, métodos HTTP e tratamento de respostas:

restClient
    .post()
    .uri("http://ai-service/api/process")
    .body(request)
    .retrieve()
    .body(Response.class);
Enter fullscreen mode Exit fullscreen mode

Com o OpenFeign, o contrato é declarado de forma explícita:

@FeignClient(name = "ai-service")
public interface AiServiceClient {

    @PostMapping("/api/v1/ai/process")
    AiResponse process(@RequestBody AiRequest request);
}
Enter fullscreen mode Exit fullscreen mode
private final AiServiceClient aiServiceClient;

public void execute(AiRequest request) {
    AiResponse response = aiServiceClient.process(request);
}
Enter fullscreen mode Exit fullscreen mode

A chamada HTTP permanece, mas passa a ser expressa como um contrato Java.

Destaca-se a diferença entre os atributos name e url. Ao declarar @FeignClient(name = "ai-service", url = "http://localhost:8082"), determina-se que o endereço especificado seja chamado diretamente. Ao declarar apenas name, solicita-se ao mecanismo de descoberta que localize uma instância disponível do serviço. Embora a diferença sintática seja pequena, o impacto sobre o acoplamento é substancial.

5. Comunicação síncrona

O OpenFeign é particularmente adequado à comunicação síncrona, na qual a etapa seguinte depende da resposta anterior:

UserDTO user = userClient.findById(userId);

if (!user.isActive()) {
    throw new BusinessException("Usuário inativo");
}

continueProcessing();
Enter fullscreen mode Exit fullscreen mode

Nesse cenário, a conversão da chamada em evento não traria benefício evidente.

A comunicação síncrona torna-se problemática quando a operação envolve múltiplas etapas demoradas. Considere-se um endpoint POST /process-audio que deve:
(1) enviar o arquivo a um modelo de IA;
(2) aguardar a transcrição;
(3) interpretar o texto;
(4) consultar outro serviço;
(5) executar uma operação;
(6) gerar áudio;
(7) retornar o resultado.

A cadeia resultante é composta por chamadas síncronas encadeadas. Se cada serviço levar alguns segundos, a requisição original permanece aberta durante todo o processo. Torna-se, então, necessário avaliar se a operação realmente exige comportamento síncrono.

6. Comunicação assíncrona: webhooks e brokers

Comunicação síncrona (REST, OpenFeign, RestClient, WebClient) é adequada quando o resultado é necessário de imediato. Na comunicação assíncrona, o serviço emissor publica um evento em um message broker (RabbitMQ, Kafka, AWS SQS, Google Pub/Sub, entre outros) e prossegue sem aguardar o processamento pelo serviço receptor.

Webhooks constituem outra forma de comunicação baseada em HTTP, com lógica distinta. Em vez de o cliente consultar repetidamente o estado da tarefa (polling), o serviço responsável notifica o cliente ao término do processamento . Para uma tarefa de 20 segundos, o polling exige sucessivas chamadas GET /jobs/123. O webhook, por sua vez, envia o resultado por meio de POST /webhooks/jobs/123 quando este estiver disponível. Essa abordagem é vantajosa quando o processamento é demorado, quando o consumidor não deve permanecer bloqueado, quando há uma fronteira clara entre os serviços e quando o produtor é capaz de notificar o consumidor.

É necessário, porém, distinguir webhooks de message brokers. O webhook permanece uma comunicação HTTP direta entre dois serviços. O broker introduz uma camada intermediária que oferece persistência de mensagens, múltiplos consumidores, retry, reprocessamento, desacoplamento temporal e controle de entrega. Assim, o webhook pode ser uma solução simples para determinadas integrações, ao passo que o broker é mais indicado para arquiteturas orientadas a eventos.

7. Arquitetura híbrida

Os modelos síncrono e assíncrono não são excludentes e podem coexistir. A escolha deve considerar a natureza de cada operação:

  • resposta necessária imediatamente: OpenFeign;
  • continuidade possível sem a resposta: evento/fila;
  • notificação de conclusão por sistema externo: webhook.

8. Integração de serviços de inteligência artificial

Uma aplicação moderna pode contar com um serviço especializado em IA. Nesse desenho, a API principal permanece responsável pelas regras de negócio, enquanto o serviço de IA interpreta linguagem, processa áudio, gera texto e respostas e integra provedores de modelos. Essa separação cria uma fronteira bem definida. Por exemplo, a instrução "Crie uma despesa de R$ 40 no iFood" é interpretada pelo serviço de IA, convertida em um CreateExpenseCommand e encaminhada à API principal, que aplica a regra de negócio e persiste o dado no PostgreSQL. O serviço de IA não acessa o banco de dados nem conhece a forma de persistência: limita-se a interpretar a intenção.

Entre os provedores possíveis, o Google Gemini mostra-se adequado a esse tipo de aplicação, sobretudo pela facilidade de integração via API e pela capacidade de processar diferentes tipos de entrada . O aspecto essencial, contudo, não é a escolha do provedor, mas evitar que o código de negócio dependa diretamente dele. A aplicação deve expressar a necessidade de "interpretar esta entrada", e não a de "chamar a API X neste ponto da regra de negócio".

O Spring AI contribui para esse objetivo ao oferecer abstrações, como o ChatClient, que separam a configuração do modelo/provedor da lógica da aplicação:

String response = chatClient
    .prompt()
    .user("Analise esta mensagem...")
    .call()
    .content();
Enter fullscreen mode Exit fullscreen mode

Essa abordagem organiza a integração e torna menos invasiva a troca ou a experimentação de modelos, pois o código da aplicação não precisa lidar com os detalhes de HTTP, autenticação e formato de payload de cada provedor.

A IA também pode operar em fluxos assíncronos. No processamento de áudio, em vez de o usuário aguardar a resposta completa, a API registra um job em uma fila e retorna 202 Accepted com um jobId. O serviço de IA processa a tarefa, consulta o Gemini e notifica a API por webhook ou evento, e a API, por fim, notifica o usuário.

9. Limites do OpenFeign e mecanismos de resiliência

O OpenFeign resolve adequadamente a necessidade de invocar outro serviço HTTP, mas não é adequado a processamentos de longa duração. Tampouco resolve automaticamente idempotência, consistência distribuída, retry seguro, circuit breaker, filas, eventos, observabilidade ou transações distribuídas. Trata-se de uma ferramenta de comunicação, e não de uma arquitetura completa.

9.1 Retry e idempotência. Considere-se um POST /payments invocado por um cliente Feign. Se a requisição alcançar o servidor e o pagamento for criado, mas a resposta se perder, o cliente pode interpretar o evento como falha. Um retry automático geraria, nesse caso, um segundo pagamento. Chamadas que alteram estado devem, portanto, ser analisadas com cautela antes de receberem retry automático. Uma solução recorrente é o uso de chaves de idempotência (Idempotency-Key: 8f3c...), que permitem ao servidor reconhecer operações já processadas.

9.2 Timeout. O tempo máximo de espera deve ser definido para cada dependência. Uma API interna pode operar com connect timeout de 2 s e read timeout de 5 s, enquanto uma chamada a um serviço de IA pode requerer 5 s e 60 s, respectivamente. Valores excessivos retêm recursos, e valores insuficientes convertem operações normais em erros. O timeout é, portanto, parte do contrato da dependência, e não apenas um parâmetro de configuração.

9.3 Circuit Breaker. Se o Gemini estiver indisponível e a API persistir em novas tentativas, sucessivos timeouts e retries podem amplificar o problema. O circuit breaker altera esse comportamento por meio de três estados: CLOSED, no qual as chamadas ocorrem normalmente; OPEN, alcançado após muitas falhas e no qual as requisições são rejeitadas rapidamente; e HALF-OPEN, no qual uma chamada de teste é realizada e, se bem-sucedida, restaura o estado CLOSED. Isso evita sobrecarregar uma dependência já em falha.

10. Visão integrada

A Figura apresenta a arquitetura completa, e a Tabela 1 sintetiza as estratégias discutidas.

Tabela 1. Estratégias de comunicação segundo a situação.

Situação Estratégia
API precisa da resposta imediatamente OpenFeign
Serviço precisa localizar outra instância Service Discovery
Operação demorada Assíncrono
Sistema externo precisa notificar a conclusão Webhook
Vários consumidores precisam receber o evento Message Broker
Integração com LLM Spring AI
Dependência instável Timeout + Circuit Breaker
Operação passível de repetição Idempotência

11. Critérios de decisão

Com base na experiência de desenvolvimento de uma aplicação SaaS, a escolha da estratégia de comunicação pode ser orientada por quatro perguntas:

  1. A resposta é necessária de imediato? Em caso afirmativo, utiliza-se HTTP/OpenFeign.
  2. É possível prosseguir sem essa resposta? Em caso afirmativo, utiliza-se evento/fila.
  3. Quem saberá quando o processamento terminar? Se for outro sistema, utiliza-se webhook.
  4. A operação pode ser executada mais de uma vez? Em caso afirmativo, deve-se garantir idempotência.

A última pergunta é particularmente relevante em sistemas distribuídos, nos quais falhas parciais passam a integrar o comportamento normal do sistema.

12. Implicações da transição de um monólito

A principal mudança na transição de um monólito não é a existência de múltiplos projetos Spring Boot, mas a aceitação de falhas intermediárias. Em um monólito, o fluxo é linear (Controller → Service → Repository → Database). Em uma arquitetura distribuída, o fluxo atravessa múltiplas fronteiras de rede (Controller → Service A → HTTP → Service B → HTTP → Service C), e cada etapa pode falhar de forma independente. Os serviços A e B podem estar operacionais e, ainda assim, a rede entre eles pode estar indisponível. Esse tipo de problema raramente é evidenciado quando os microsserviços são estudados apenas por meio de diagramas.

13. Conclusão

A adoção de microsserviços não se resume à divisão de uma aplicação em vários projetos. A existência de múltiplos processos exige atenção a comunicação, descoberta de serviços, timeout, falhas, idempotência e consistência. O Spring Cloud oferece ferramentas relevantes nesse sentido: o OpenFeign simplifica as chamadas HTTP entre serviços, o Service Discovery elimina o acoplamento a endereços físicos, o LoadBalancer viabiliza o uso de múltiplas instâncias, webhooks e eventos retiram operações demoradas do caminho síncrono, e o Spring AI facilita a integração com modelos de IA, incluindo o Gemini. Os mecanismos de resiliência, por sua vez, permitem lidar com o fato inevitável de que, em um sistema distribuído, falhas ocorrerão.

Conclui-se que a escolha entre comunicação síncrona, assíncrona, OpenFeign, webhooks ou mensageria deve ser orientada pela dependência entre as etapas do negócio, e não pela sofisticação aparente da tecnologia. Se a etapa seguinte depende da resposta, a chamada síncrona é adequada. Se pode aguardar, um evento tende a ser preferível. Se um sistema externo notifica a conclusão, o webhook pode simplificar a arquitetura.

Por fim, os microsserviços não eliminam o acoplamento: eles o transformam de acoplamento de código em acoplamento de comunicação, o que exige que essa comunicação seja projetada de forma deliberada.


Top comments (0)