1. Introdução
Neste codelab, você vai usar o gRPC-Java para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em Java.
Ao final do tutorial, você terá um cliente que se conecta a um servidor remoto usando o gRPC para receber o nome ou o endereço postal do que está localizado em coordenadas específicas em um mapa. Um aplicativo completo pode usar esse design cliente-servidor para enumerar ou resumir pontos de interesse ao longo de um trajeto.
A API do servidor é definida em um arquivo de buffers de protocolo, que será usado para gerar código boilerplate para o cliente e o servidor, permitindo que eles se comuniquem entre si e economizando tempo e esforço na implementação dessa funcionalidade.
Esse código gerado cuida não apenas das complexidades da comunicação entre o servidor e o cliente, mas também da serialização e desserialização de dados.
O que você vai aprender
- Como usar buffers de protocolo para definir uma API de serviço.
- Como criar um cliente e um servidor baseados em gRPC com uma definição de buffers de protocolo usando a geração automática de código.
- Entendimento da comunicação cliente-servidor com gRPC.
Este codelab é destinado a desenvolvedores Java que não conhecem o gRPC ou querem relembrar o assunto, além de qualquer pessoa interessada em criar sistemas distribuídos. Não é necessário ter experiência com gRPC.
2. Antes de começar
Pré-requisitos
- JDK versão 8 ou mais recente
Acessar o código
Para que você não precise começar do zero, este codelab oferece um scaffold do código-fonte do aplicativo para você concluir. As etapas a seguir mostram como concluir o aplicativo, incluindo o uso dos plug-ins do compilador de buffer de protocolo para gerar o código gRPC boilerplate.
Primeiro, crie o diretório de trabalho do codelab e use cd para acessar ele:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Faça o download e extraia o codelab:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here
Como alternativa, baixe o arquivo .zip que contém apenas o diretório do codelab e descompacte-o manualmente.
O código-fonte completo está disponível no GitHub se você quiser pular a digitação de uma implementação.
3. Definir o serviço
A primeira etapa é definir o serviço gRPC do aplicativo, o método RPC e os tipos de mensagens de solicitação e resposta usando buffers de protocolo. Seu serviço vai oferecer:
- Um método RPC chamado
GetFeature
que o servidor implementa e o cliente chama. - Os tipos de mensagem
Point
eFeature
, que são estruturas de dados trocadas entre o cliente e o servidor ao usar o métodoGetFeature
. O cliente fornece coordenadas do mapa como umPoint
na solicitaçãoGetFeature
ao servidor, e o servidor responde com umFeature
correspondente que descreve o que está localizado nessas coordenadas.
Esse método RPC e os tipos de mensagem dele serão definidos no arquivo src/main/proto/routeguide/route_guide.proto
do código-fonte fornecido.
Os buffers de protocolo são conhecidos como protobufs. Para mais informações sobre a terminologia do gRPC, consulte Conceitos principais, arquitetura e ciclo de vida do gRPC.
Como estamos gerando código Java neste exemplo, especificamos uma opção de arquivo java_package
e um nome para a classe Java no nosso .proto
:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Tipos de mensagem
No arquivo routeguide/route_guide.proto
do código-fonte, primeiro defina o tipo de mensagem Point
. Um Point
representa um par de coordenadas de latitude e longitude em um mapa. Neste codelab, use números inteiros para as coordenadas:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Os números 1
e 2
são IDs exclusivos para cada um dos campos na estrutura message
.
Em seguida, defina o tipo de mensagem Feature
. Um Feature
usa um campo string
para o nome ou endereço postal de algo em um local especificado por um Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Método de serviço
O arquivo route_guide.proto
tem uma estrutura service
chamada RouteGuide
que define um ou mais métodos fornecidos pelo serviço do aplicativo.
Adicione o método rpc
GetFeature
à definição RouteGuide
. Como explicado anteriormente, esse método vai pesquisar o nome ou endereço de um local em um determinado conjunto de coordenadas. Portanto, faça com que GetFeature
retorne um Feature
para um determinado Point
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Esse é um método RPC unário: um RPC simples em que o cliente envia uma solicitação ao servidor e aguarda uma resposta, assim como uma chamada de função local.
4. Gerar código de cliente e servidor
Em seguida, precisamos gerar as interfaces do cliente e do servidor gRPC com base na definição do serviço .proto
. Para isso, usamos o compilador de buffer de protocolo protoc
com um plug-in especial do gRPC Java. É necessário usar o compilador proto3 (que oferece suporte à sintaxe proto2 e proto3) para gerar serviços gRPC.
Ao usar o Gradle ou o Maven, o plug-in de build protoc
pode gerar o código necessário como parte do build. Consulte o README do grpc-java para saber como gerar código dos seus próprios arquivos .proto
.
Fornecemos um ambiente e uma configuração do Gradle no código-fonte do codelab para criar esse projeto.
No diretório grpc-java-getting-started
, execute o seguinte comando:
$ chmod +x gradlew $ ./gradlew generateProto
As seguintes classes são geradas da nossa definição de serviço:
Feature.java
,Point.java
e outros que contêm todo o código do buffer de protocolo para preencher, serializar e recuperar nossos tipos de mensagens de solicitação e resposta.RouteGuideGrpc.java
, que contém (junto com outros códigos úteis) uma classe base para os servidoresRouteGuide
implementarem,RouteGuideGrpc.RouteGuideImplBase
, com todos os métodos definidos no serviçoRouteGuide
e classes de stub para uso dos clientes.
5. Implementar o servidor
Primeiro, vamos ver como criar um servidor RouteGuide
. Há duas partes para fazer nosso serviço RouteGuide
funcionar:
- Implementar a interface de serviço gerada na definição de serviço, que faz o "trabalho" real do serviço.
- Executar um servidor gRPC para detectar solicitações de clientes e enviá-las à implementação de serviço correta.
Implementar o RouteGuide
Como você pode ver, nosso servidor tem uma classe RouteGuideService
que estende a classe abstrata RouteGuideGrpc.RouteGuideImplBase
gerada:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
Fornecemos os dois arquivos a seguir para inicializar seu servidor com recursos:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
Vamos analisar uma implementação simples de RPC em detalhes.
RPC unária
RouteGuideService
implementa todos os nossos métodos de serviço. Nesse caso, é apenas GetFeature()
, que recebe uma mensagem Point
do cliente e retorna em uma mensagem Feature
as informações de local correspondentes de uma lista de lugares conhecidos.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
O método getFeature()
usa dois parâmetros:
Point
: a solicitação.StreamObserver<Feature>
: um observador de resposta, que é uma interface especial para o servidor chamar com a resposta dele.
Para retornar nossa resposta ao cliente e concluir a chamada:
- Construímos e preenchemos um objeto de resposta
Feature
para retornar ao cliente, conforme especificado na definição do serviço. Neste exemplo, fazemos isso em um métodocheckFeature()
particular separado. - Usamos o método
onNext()
do observador de resposta para retornar oFeature
. - Usamos o método
onCompleted()
do observador de resposta para especificar que terminamos de lidar com a RPC.
Iniciar o servidor
Depois de implementar todos os métodos de serviço, precisamos iniciar um servidor gRPC para que os clientes possam usar nosso serviço. Incluímos no nosso boilerplate a criação do objeto ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
Vamos criar o serviço no construtor:
- Especifique a porta que queremos usar para detectar solicitações do cliente usando o método
forPort()
do builder (ele usará o endereço curinga). - Crie uma instância da classe de implementação do serviço
RouteGuideService
e transmita-a ao métodoaddService()
do builder. - Chame
build()
no builder para criar um servidor RPC para nosso serviço.
O snippet a seguir mostra como criar um objeto ServerBuilder
.
/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
port, RouteGuideUtil.parseFeatures(featureFile));
}
O snippet a seguir mostra como criar um objeto de servidor para nosso serviço RouteGuide
.
/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
this.port = port;
server = serverBuilder.addService(new RouteGuideService(features))
.build();
}
Implemente um método de início que chame start
no servidor criado acima.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Implemente um método para aguardar a conclusão do servidor para que ele não seja encerrado imediatamente.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
Como você pode ver, criamos e iniciamos nosso servidor usando um ServerBuilder
.
No método principal, fazemos o seguinte:
- Crie uma instância
RouteGuideServer
. - Chame
start()
para ativar um servidor RPC para nosso serviço. - Aguarde a interrupção do serviço chamando
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. Criar o cliente
Nesta seção, vamos criar um cliente para nosso serviço RouteGuide
.
Instanciar um stub
Para chamar métodos de serviço, primeiro precisamos criar um stub. Existem dois tipos de stubs, mas só precisamos usar o de bloqueio para este codelab. Os dois tipos são:
- um stub de bloqueio/síncrono que faz uma chamada de RPC e aguarda a resposta do servidor, retornando uma resposta ou gerando uma exceção.
- um stub não bloqueador/assíncrono que faz chamadas não bloqueadoras para o servidor, em que a resposta é retornada de forma assíncrona. Você só pode fazer alguns tipos de chamadas de streaming usando o stub assíncrono.
Primeiro, precisamos criar um canal gRPC e usá-lo para criar nosso stub.
Poderíamos ter usado um ManagedChannelBuilder
diretamente para criar o canal.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
Mas vamos usar um método utilitário que usa uma string com hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
Agora podemos usar o canal para criar nosso stub de bloqueio. Neste codelab, temos apenas RPCs de bloqueio. Por isso, usamos o método newBlockingStub
fornecido na classe RouteGuideGrpc
que geramos do nosso .proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
Chamar métodos de serviço
Agora vamos ver como chamamos os métodos do serviço.
RPC simples
Chamar o RPC simples GetFeature
é quase tão simples quanto chamar um método local.
Criamos e preenchemos um objeto de buffer do protocolo de solicitação (no nosso caso, Point
), transmitimos para o método getFeature()
no nosso stub de bloqueio e recebemos um Feature
.
Se ocorrer um erro, ele será codificado como um Status
, que pode ser obtido do StatusRuntimeException
.
Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
Feature feature;
try {
feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
O boilerplate registra uma mensagem com o conteúdo com base na presença ou não de um recurso no ponto especificado.
7. Faça um teste
- No diretório
start_here
, execute o seguinte comando:
$ ./gradlew installDist
Isso vai compilar seu código, empacotá-lo em um jar e criar os scripts que executam o exemplo. Eles serão criados no diretório build/install/start_here/bin/
. Os scripts são: route-guide-server
e route-guide-client
.
O servidor precisa estar em execução antes de iniciar o cliente.
- Execute o servidor:
$ ./build/install/start_here/bin/route-guide-server
- Execute o cliente:
$ ./build/install/start_here/bin/route-guide-client
Você vai ver uma saída como esta, com carimbos de data/hora omitidos para maior clareza:
INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906 INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619 INFO: *** GetFeature: lat=0 lon=0 INFO: Found no feature at 0, 0
8. A seguir
- Saiba como o gRPC funciona em Introdução ao gRPC e Conceitos principais.
- Siga as etapas do tutorial de noções básicas.
- Confira a referência da API.