O gRPC é um framework de chamada de procedimento remoto (RPC) moderno e de alto desempenho, originalmente desenvolvido pelo Google. Ele utiliza o Protocol Buffers (protobuf) como sua Interface Definition Language (IDL) para definir a estrutura dos serviços e mensagens, e o HTTP/2 como protocolo de transporte, oferecendo suporte nativo a streams bidirecionais e multiplexação de requisições. Seu uso se torna cada vez mais relevante em arquiteturas de microsserviços e ambientes cloud-native, devido à sua eficiência, interoperabilidade entre diferentes linguagens e suporte a recursos como autenticação, balanceamento de carga e observabilidade.
Conceitos Fundamentais
RPC (Remote Procedure Call): Um paradigma que permite a uma aplicação chamar uma função ou método em outro endereço de rede, como se fosse uma invocação local.
IDL (Interface Definition Language): Uma linguagem para descrever a interface de um serviço de software de forma independente da linguagem de programação. No gRPC, o Protocol Buffers é a IDL padrão.
Protocol Buffers (protobuf): Um mecanismo de serialização de dados neutro, eficiente e extensível. Ele define a estrutura dos dados (mensagens) e dos serviços (com métodos e tipos) em arquivos .proto.
Tipos de Chamada: O gRPC suporta quatro padrões de comunicação principais: chamadas unárias (simples request/response), streaming do servidor (o servidor envia uma sequência de respostas), streaming do cliente (o cliente envia uma sequência de requisições) e streaming bidirecional (ambos os lados enviam streams).
Exemplo Prático em Java
Vamos construir um serviço gRPC em Java que demonstra os diferentes tipos de chamada. O projeto utiliza Maven para gerenciamento de dependências e geração de código a partir dos arquivos .proto.
1. Definição do Serviço com Protocol Buffers
Crie o arquivo src/main/proto/greetings.proto. A definição abaixo especifica quatro métodos, cada um representando um tipo de chamada diferente.
syntax = "proto3";
package tutorial;
option java_package = "com.example.grpc.tutorial";
option java_multiple_files = true;
service Greeter {
rpc SayHelloUnary (GreetingRequest) returns (GreetingResponse);
rpc SayHelloServerStream (GreetingRequest) returns (stream GreetingResponse);
rpc SayHelloClientStream (stream GreetingRequest) returns (GreetingResponse);
rpc SayHelloBidirectional (stream GreetingRequest) returns (stream GreetingResponse);
}
message GreetingRequest {
string person_name = 1;
}
message GreetingResponse {
string greeting_message = 1;
}
2. Configruação do Maven para Geração de Código
Adicione o plugin protobuf-maven-plugin ao seu pom.xml. Ao executar mvn compile, o plugin gera automaticamente as classes Java correspondentes à definição do serviço e às mensagens.
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.21.12:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.54.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3. Implementação do Servidor
A lógica do serviço reside na classe que estende GreeterGrpc.GreeterImplBase. Cada método é sobrescrito para tratar o tipo de chamada correspondente.
package com.example.grpc.server;
import com.example.grpc.tutorial.*;
import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;
public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase {
// Chamada Unária
@Override
public void sayHelloUnary(GreetingRequest req, StreamObserver<GreetingResponse> responseObserver) {
GreetingResponse reply = GreetingResponse.newBuilder()
.setGreetingMessage("Olá, " + req.getPersonName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
// Streaming do Servidor
@Override
public void sayHelloServerStream(GreetingRequest req, StreamObserver<GreetingResponse> responseObserver) {
String name = req.getPersonName();
for (int i = 1; i <= 5; i++) {
GreetingResponse reply = GreetingResponse.newBuilder()
.setGreetingMessage("Saudação #" + i + " para " + name)
.build();
responseObserver.onNext(reply);
}
responseObserver.onCompleted();
}
// Streaming do Cliente
@Override
public StreamObserver<GreetingRequest> sayHelloClientStream(StreamObserver<GreetingResponse> responseObserver) {
return new StreamObserver<GreetingRequest>() {
final List<String> receivedNames = new ArrayList<>();
@Override
public void onNext(GreetingRequest value) {
receivedNames.add(value.getPersonName());
}
@Override
public void onError(Throwable t) {
System.err.println("Erro no stream do cliente: " + t.getMessage());
}
@Override
public void onCompleted() {
String allNames = String.join(", ", receivedNames);
GreetingResponse reply = GreetingResponse.newBuilder()
.setGreetingMessage("Olá para todos: " + allNames)
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
};
}
// Streaming Bidirecional
@Override
public StreamObserver<GreetingRequest> sayHelloBidirectional(StreamObserver<GreetingResponse> responseObserver) {
return new StreamObserver<GreetingRequest>() {
@Override
public void onNext(GreetingRequest req) {
// Para cada requisição recebida, envia uma resposta imediatamente
GreetingResponse reply = GreetingResponse.newBuilder()
.setGreetingMessage("Eco: Olá, " + req.getPersonName())
.build();
responseObserver.onNext(reply);
}
@Override
public void onError(Throwable t) {
System.err.println("Erro no stream bidirecional: " + t.getMessage());
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}
Para iniciar o servidor:
import io.grpc.Server;
import io.grpc.ServerBuilder;
public class GrpcServer {
public static void main(String[] args) throws Exception {
Server server = ServerBuilder.forPort(8080)
.addService(new GreeterServiceImpl())
.build()
.start();
System.out.println("Servidor gRPC iniciado na porta 8080");
server.awaitTermination();
}
}
4. Implementação do Cliente
O cliente estabelece um canal com o servidor e cria um stub (cliente) para invocar os métodos do serviço.
package com.example.grpc.client;
import com.example.grpc.tutorial.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
public class GrpcClient {
public static void main(String[] args) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
// Usando stub assíncrono para todos os exemplos
GreeterGrpc.GreeterStub asyncStub = GreeterGrpc.newStub(channel);
// 1. Chamada Unária
System.out.println("Testando chamada unária...");
GreetingRequest unaryRequest = GreetingRequest.newBuilder().setPersonName("Alice").build();
asyncStub.sayHelloUnary(unaryRequest, new StreamObserver<GreetingResponse>() {
@Override
public void onNext(GreetingResponse value) {
System.out.println("Resposta Unária: " + value.getGreetingMessage());
}
@Override
public void onError(Throwable t) { t.printStackTrace(); }
@Override
public void onCompleted() { System.out.println("Unária concluída."); }
});
// 2. Streaming do Servidor
System.out.println("\nTestando streaming do servidor...");
GreetingRequest serverStreamReq = GreetingRequest.newBuilder().setPersonName("Bob").build();
asyncStub.sayHelloServerStream(serverStreamReq, new StreamObserver<GreetingResponse>() {
@Override
public void onNext(GreetingResponse value) {
System.out.println("Resposta do Servidor (Stream): " + value.getGreetingMessage());
}
@Override
public void onError(Throwable t) { t.printStackTrace(); }
@Override
public void onCompleted() { System.out.println("Streaming do servidor concluído."); }
});
// 3. Streaming do Cliente
System.out.println("\nTestando streaming do cliente...");
CountDownLatch clientLatch = new CountDownLatch(1);
StreamObserver<GreetingRequest> clientStreamObserver = asyncStub.sayHelloClientStream(new StreamObserver<GreetingResponse>() {
@Override
public void onNext(GreetingResponse value) {
System.out.println("Resposta do Cliente (Stream): " + value.getGreetingMessage());
}
@Override
public void onError(Throwable t) { t.printStackTrace(); }
@Override
public void onCompleted() {
System.out.println("Streaming do cliente concluído.");
clientLatch.countDown();
}
});
clientStreamObserver.onNext(GreetingRequest.newBuilder().setPersonName("Carlos").build());
clientStreamObserver.onNext(GreetingRequest.newBuilder().setPersonName("Diana").build());
clientStreamObserver.onCompleted();
clientLatch.await();
// 4. Streaming Bidirecional
System.out.println("\nTestando streaming bidirecional...");
CountDownLatch bidiLatch = new CountDownLatch(1);
StreamObserver<GreetingRequest> bidiObserver = asyncStub.sayHelloBidirectional(new StreamObserver<GreetingResponse>() {
@Override
public void onNext(GreetingResponse value) {
System.out.println("Resposta Bidirecional: " + value.getGreetingMessage());
}
@Override
public void onError(Throwable t) { t.printStackTrace(); }
@Override
public void onCompleted() {
System.out.println("Streaming bidirecional concluído.");
bidiLatch.countDown();
}
});
bidiObserver.onNext(GreetingRequest.newBuilder().setPersonName("Eva").build());
bidiObserver.onNext(GreetingRequest.newBuilder().setPersonName("Fernando").build());
bidiObserver.onCompleted();
bidiLatch.await();
channel.shutdown();
}
}
Execute primeiro a classe GrpcServer e, em seguida, a GrpcClient. O console do servidor mostrará as requisições recebidas e as respostas enviadas, enquanto o console do cliente exibirá as respostas recebidas do servidor para cada tipo de chamada gRPC.