Se você já leu os dois últimos artigos sobre gRPC, viu como essa tecnologia pode simplificar a comunicação entre microsserviços. Por exemplo, no artigo anterior, Tutorial Completo: Criando uma Unary API gRPC em Java, aprendemos a criar um endpoint que responde a uma única solicitação. Agora, vamos explorar um recurso ainda mais versátil: a Server Streaming API.
Esse tipo de API é ideal para cenários onde o servidor precisa enviar uma grande quantidade de dados (payloads grandes) ou atualizações contínuas para o cliente, sem que este precise realizar múltiplas requisições. Ao invés disso, uma única conexão é aberta, permitindo ao servidor enviar múltiplas respostas de forma eficiente.
Neste artigo, você aprenderá:
- O que é uma Server Streaming API e suas vantagens.
- Como definir o contrato
.protopara implementar essa funcionalidade. - Passo a passo para criar essa API em Java.
Além disso, o código completo deste tutorial está disponível no GitHub. Você pode conferir o repositório aqui.
Vamos começar?
O Que é uma Server Streaming API no gRPC?
A Server Streaming API é uma forma de comunicação onde o servidor envia múltiplas mensagens ao cliente, todas dentro de uma única conexão. Por outro lado uma Unary API, que responde com uma única mensagem, a Server Streaming API permite que o cliente faça apenas uma solicitação e receba várias respostas do servidor ao longo do tempo.
Esse modelo é especialmente útil em situações onde:
- O servidor precisa transmitir grandes volumes de dados em partes, sem sobrecarregar a rede.
- O cliente deve receber atualizações contínuas sem precisar enviar novas requisições.
Como Funciona a Server Streaming API
Para entender melhor, veja o fluxo básico:
- Primeiro, o cliente envia uma solicitação para o servidor.
- Em seguida, o servidor começa a responder com uma sequência de mensagens.
- Por fim, a conexão é encerrada assim que todas as mensagens forem enviadas.
Essa abordagem mantém a conexão ativa durante todo o envio, reduzindo o overhead causado por múltiplas requisições. Além disso, ela melhora a eficiência da comunicação em casos onde o cliente precisa de grandes volumes de dados ou informações em tempo real.
Exemplo Prático: Server Streaming API para Números Ímpares
Para ilustrar o funcionamento de uma Server Streaming API no gRPC, vamos construir um serviço simples. Ele permitirá ao cliente enviar um intervalo numérico (por exemplo, de 1 a 10) e receber, como resposta, todos os números ímpares dentro desse intervalo.
Definindo o Contrato Protobuf para a API
Nesta etapa, vamos definir o contrato Protobuf para o nosso serviço gRPC. Esse contrato será a base da comunicação entre cliente e servidor, especificando as mensagens e o método que processará o intervalo numérico para retornar os números ímpares.
syntax = "proto3";
package gerador;
option java_multiple_files = true;
option java_package = "com.gerador.grpc";
option java_outer_classname = "GeradorNumerosProto";
// Mensagem de solicitação contendo o intervalo numérico
message IntervaloRequest {
int32 inicio = 1; // Número inicial do intervalo
int32 fim = 2; // Número final do intervalo
}
// Mensagem de resposta contendo um número ímpar
message NumeroImparResponse {
int32 numero = 1; // Número ímpar retornado
}
service GeradorNumeros {
// Método para retornar números impares dentro de um intervalo
rpc GerarImpares (IntervaloRequest) returns (stream NumeroImparResponse);
} Explicando as Principais Palavras-Chave do Protobuf
- syntax = “proto3”; Esta linha indica que estamos usando a versão 3 do Protobuf, que é atualmente a mais utilizada. Ela simplifica a definição de contratos e adiciona suporte a novos recursos, como valores opcionais.
- package gerador; O pacote organiza o código gerado, garantindo que as classes e os métodos não entrem em conflito com outros serviços no mesmo projeto.
- option java_multiple_files = true; Gera múltiplos arquivos .java para as mensagens e serviços definidos. Isso melhora a organização do projeto e facilita a manutenção do código.
- message IntervaloRequest e message NumeroImparResponse Essas mensagens representam os dados trocados entre cliente e servidor:
- service GeradorNumeros Esta é a definição do serviço, que agrupa os métodos disponíveis. Neste caso, temos o método GerarImpares.
- rpc GerarImpares (IntervaloRequest) returns (stream NumeroImparResponse);
- rpc: Define um método remoto (Remote Procedure Call).
- stream: Indica que o servidor pode enviar múltiplas respostas ao cliente em uma única conexão. Isso é o que caracteriza a Server Streaming API.
- IntervaloRequest: A mensagem enviada pelo cliente.
- NumeroImparRespons: As mensagens retornadas pelo servidor.
Implementando o Serviço gRPC: Lógica de Números Ímpares
Se você acompanhou o artigo anterior, verá que a implementação do método gerarImpares tem muitas semelhanças com a Unary Operation que discutimos anteriormente. A principal diferença aqui é o uso de um fluxo contínuo de respostas para o cliente, caracterizando a Server Streaming API.
Abaixo está o código completo:
package tech.aartedeprogramar.services;
import com.gerador.grpc.GeradorNumerosGrpc;
import com.gerador.grpc.IntervaloRequest;
import com.gerador.grpc.NumeroImparResponse;
import io.grpc.stub.StreamObserver;
public class GeradorNumerosServiceImpl extends GeradorNumerosGrpc.GeradorNumerosImplBase {
@Override
public void gerarImpares(IntervaloRequest request, StreamObserver<NumeroImparResponse> responseObserver) {
int inicio = request.getInicio();
int fim = request.getFim();
if (inicio > fim) {
responseObserver.onError(new IllegalArgumentException("O valor inicial deve ser menor ou igual ao valor final."));
return;
}
while (inicio <= fim) {
if (isImpar(inicio)) {
NumeroImparResponse response = NumeroImparResponse.newBuilder()
.setNumero(inicio)
.build();
responseObserver.onNext(response);
sleep(500);
}
inicio++;
}
responseObserver.onCompleted();
}
private void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static boolean isImpar(int inicio) {
return inicio % 2 != 0;
}
}
Explicação do Código
Herança da Classe Base:
A classeGeradorNumerosServiceImplestende a classeGeradorNumerosGrpc.GeradorNumerosImplBase, gerada automaticamente a partir do arquivo Protobuf. Essa herança é obrigatória para implementar os métodos definidos no contrato.Tratamento de Erros com
responseObserver.onError():
Caso o valor inicial do intervalo seja maior que o final, o servidor envia um erro para o cliente, interrompendo a execução. Essa validação evita inconsistências na lógica.
Neste exemplo, usamos uma exceção genérica (IllegalArgumentException), mas em um cenário real é importante usar gRPC Status Codes para categorizar melhor os erros. Falaremos mais sobre os Status Codes em um artigo futuro.Enviando Respostas com
responseObserver.onNext():
Para cada número ímpar identificado, o servidor cria uma instância deNumeroImparResponsee a envia ao cliente. Esse método é chamado repetidamente até que todos os números do intervalo tenham sido processados.Simulação de Processamento com
sleep():
O métodosleepadiciona um atraso de 500ms entre o envio de cada resposta. Isso simula o comportamento de um processamento mais demorado, comum em cenários reais.Finalizando o Streaming com
responseObserver.onCompleted():
Após enviar todas as respostas, o métodoonCompletedé chamado para indicar ao cliente que o fluxo foi concluído e o servidor não enviará mais mensagens.
Configurando o Servidor gRPC
No artigo anterior, configuramos o servidor gRPC para hospedar nossos serviços. Para adicionar o novo serviço GeradorNumerosServiceImpl, basta registrar a implementação no servidor, usando o método addService durante a criação do Server.
A linha que deve ser adicionada ou ajustada é:
Server server = ServerBuilder.forPort(50051)
.addService(new CalculadoraServiceImpl())
.addService(new GeradorNumerosServiceImpl())
.build(); Com isso, o servidor gRPC estará pronto para atender às solicitações do cliente e retornar os números ímpares dentro do intervalo especificado.
Criando o Cliente gRPC: Solicitando Números Ímpares
Agora que o servidor está configurado e o serviço de números ímpares está ativo, vamos implementar um cliente gRPC para testar a funcionalidade. O cliente será responsável por enviar um intervalo ao servidor e processar as respostas recebidas.
Abaixo está o código completo do cliente:
package tech.aartedeprogramar.client;
import com.gerador.grpc.GeradorNumerosGrpc;
import com.gerador.grpc.IntervaloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class GeradorNumerosCliente {
public static void main(String[] args) {
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
GeradorNumerosGrpc.GeradorNumerosBlockingStub stub = GeradorNumerosGrpc.newBlockingStub(managedChannel);
IntervaloRequest request = IntervaloRequest.newBuilder()
.setInicio(1)
.setFim(50)
.build();
System.out.println("Números ímpares recebidos:");
stub.gerarImpares(request)
.forEachRemaining(response -> System.out.println(response.getNumero()));
managedChannel.shutdown();
}
}
Diferenças em Relação à Unary Operation
Recebendo Múltiplas Respostas:
- Em vez de receber uma única resposta, o método
gerarImparesretorna um fluxo de respostas. - Para processar esse fluxo, usamos
forEachRemaining, que itera sobre cada mensagem enviada pelo servidor.
- Em vez de receber uma única resposta, o método
Iteração no Streaming:
- O cliente não precisa aguardar que todas as mensagens sejam enviadas antes de começar a processá-las. Assim que o servidor envia uma resposta, o cliente a processa imediatamente.
- Isso é útil em cenários onde a latência precisa ser reduzida ou quando os dados são gerados continuamente.
Streaming Contínuo:
- No console, você verá os números ímpares sendo exibidos um por vez, com um pequeno intervalo entre eles. Esse comportamento é diferente de uma Unary Operation, onde os dados são enviados e recebidos de uma vez.
Com essas mudanças, o cliente está pronto para consumir os números ímpares enviados pelo servidor em tempo real. Para testar:
- Certifique-se de que o servidor gRPC está em execução.
- Execute o cliente. Você verá a lista de números ímpares no console, exibidos sequencialmente.
Testando com Postman
Se preferir, também é possível testar este serviço utilizando o Postman. No primeiro artigo, explicamos como configurar o Postman para consumir APIs gRPC. Essa abordagem é útil se você quiser testar sem implementar um cliente ou explorar os detalhes do request e response diretamente.
Conclusão
Neste artigo, exploramos como criar uma Server Streaming API utilizando gRPC em Java. Partimos do conceito inicial e avançamos pela implementação do serviço, configuração do servidor, e criação do cliente gRPC. Além disso, destacamos as diferenças principais entre uma Unary Operation e uma Server Streaming API, o que é fundamental para entender as vantagens do streaming contínuo.
Com isso, você aprendeu a:
- Definir um contrato Protobuf para uma Server Streaming API.
- Implementar um serviço que envia múltiplas respostas ao cliente.
- Configurar o cliente para processar respostas contínuas em tempo real.
Se você seguiu o passo a passo, agora está pronto para integrar uma Server Streaming API em seus próprios projetos!
Código no GitHub
Todo o código deste artigo está disponível em nosso repositório no GitHub. Acesse para explorar e testar os exemplos diretamente no seu ambiente.
💬 Gostou deste artigo? Deixe seu comentário abaixo com dúvidas, sugestões ou ideias para futuros conteúdos. E se achar útil, não esqueça de compartilhar com seus amigos e colegas! 🚀
Referências
- gRPC: Introdução e Documentação Oficial – Fonte oficial do gRPC, com detalhes sobre suas funcionalidades, arquitetura e uso.
- Livro: gRPC: Up and Running: Building High-Performance Microservices with gRPC, disponível em O’Reilly. Esse livro fornece uma visão aprofundada do gRPC em Java, ideal para quem deseja expandir seus conhecimentos.
Quer praticar esse tipo de problema do jeito que cai em entrevistas?
Estou construindo o DevPrep, uma plataforma em português focada em raciocínio e padrões de algoritmos.
👉 Acesso por beta fechado: https://devprep.dev/