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 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") 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 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); 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); } private final AiServiceClient aiServiceClient; public void execute(AiRequest request) { AiResponse response = aiServiceClient.process(request); } 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(); 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(); 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
Comments
No comments yet. Start the discussion.