1. Introducción
En este codelab, usarás gRPC-Java para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Java.
Al final del instructivo, tendrás un cliente que se conecta a un servidor remoto con gRPC para obtener el nombre o la dirección postal de lo que se encuentra en coordenadas específicas en un mapa. Una aplicación completa podría usar este diseño cliente-servidor para enumerar o resumir los puntos de interés a lo largo de una ruta.
La API del servidor se define en un archivo de búferes de protocolo, que se usará para generar código estándar para el cliente y el servidor, de modo que puedan comunicarse entre sí, lo que te ahorrará tiempo y esfuerzo en la implementación de esa funcionalidad.
Este código generado se encarga no solo de las complejidades de la comunicación entre el servidor y el cliente, sino también de la serialización y deserialización de datos.
Qué aprenderás
- Cómo usar los búferes de protocolo para definir una API de servicio
- Cómo compilar un cliente y un servidor basados en gRPC a partir de una definición de Protocol Buffers con la generación de código automatizada
- Conocimiento de la comunicación cliente-servidor con gRPC
Este codelab está dirigido a desarrolladores de Java que no conocen gRPC o que desean repasar sus conceptos, o bien a cualquier persona interesada en crear sistemas distribuidos. No se requiere experiencia previa con gRPC.
2. Antes de comenzar
Requisitos previos
- JDK versión 8 o posterior
Obtén el código
Para que no tengas que empezar desde cero, este codelab proporciona un andamio del código fuente de la aplicación para que lo completes. En los siguientes pasos, se muestra cómo finalizar la aplicación, incluido el uso de los complementos del compilador de búfer de protocolo para generar el código gRPC estándar.
Primero, crea el directorio de trabajo del codelab y cámbiate a él con el comando cd:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Descarga y extrae el 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
También puedes descargar el archivo .zip que contiene solo el directorio del codelab y descomprimirlo de forma manual.
El código fuente completo está disponible en GitHub si no quieres escribir una implementación.
3. Define el servicio
El primer paso es definir el servicio de gRPC de la aplicación, su método de RPC y sus tipos de mensajes de solicitud y respuesta con búferes de protocolo. Tu servicio proporcionará lo siguiente:
- Un método de RPC llamado
GetFeature
que el servidor implementa y el cliente llama. - Son los tipos de mensajes
Point
yFeature
que son estructuras de datos que se intercambian entre el cliente y el servidor cuando se usa el métodoGetFeature
. El cliente proporciona coordenadas de mapa como unPoint
en su solicitudGetFeature
al servidor, y el servidor responde con unFeature
correspondiente que describe lo que se encuentra en esas coordenadas.
Este método de RPC y sus tipos de mensajes se definirán en el archivo src/main/proto/routeguide/route_guide.proto
del código fuente proporcionado.
Los búferes de protocolo se conocen comúnmente como protobufs. Para obtener más información sobre la terminología de gRPC, consulta los conceptos básicos, la arquitectura y el ciclo de vida de gRPC.
Como generaremos código Java en este ejemplo, especificamos una opción de archivo java_package
y un nombre para la clase Java en nuestro .proto
:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Tipos de mensajes
En el archivo routeguide/route_guide.proto
del código fuente, primero define el tipo de mensaje Point
. Un objeto Point
representa un par de coordenadas de latitud y longitud en un mapa. En este codelab, usa números enteros para las coordenadas:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Los números 1
y 2
son números de ID únicos para cada uno de los campos de la estructura message
.
A continuación, define el tipo de mensaje Feature
. Un Feature
usa un campo string
para el nombre o la dirección postal de algo en una ubicación especificada por un 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 servicio
El archivo route_guide.proto
tiene una estructura service
llamada RouteGuide
que define uno o más métodos proporcionados por el servicio de la aplicación.
Agrega el método rpc
GetFeature
dentro de la definición de RouteGuide
. Como se explicó anteriormente, este método buscará el nombre o la dirección de una ubicación a partir de un conjunto de coordenadas determinado, por lo que GetFeature
devolverá un Feature
para un Point
determinado:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Este es un método de RPC unario: una RPC simple en la que el cliente envía una solicitud al servidor y espera a que vuelva una respuesta, al igual que una llamada a función local.
4. Genera código de cliente y servidor
A continuación, debemos generar las interfaces de cliente y servidor de gRPC a partir de nuestra definición de servicio .proto
. Para ello, usamos el compilador de búfer de protocolo protoc
con un complemento especial de gRPC Java. Debes usar el compilador proto3 (que admite la sintaxis de proto2 y proto3) para generar servicios de gRPC.
Cuando se usa Gradle o Maven, el complemento de compilación protoc
puede generar el código necesario como parte de la compilación. Puedes consultar el README de grpc-java para saber cómo generar código a partir de tus propios archivos .proto
.
Proporcionamos un entorno y una configuración de Gradle en el código fuente del codelab para compilar este proyecto.
Dentro del directorio grpc-java-getting-started
, ejecuta el siguiente comando:
$ chmod +x gradlew $ ./gradlew generateProto
Las siguientes clases se generan a partir de nuestra definición de servicio:
Feature.java
,Point.java
y otros que contienen todo el código de búfer de protocolo para completar, serializar y recuperar nuestros tipos de mensajes de solicitud y respuesta.RouteGuideGrpc.java
, que contiene (junto con otro código útil) una clase base para que implementen los servidoresRouteGuide
,RouteGuideGrpc.RouteGuideImplBase
, con todos los métodos definidos en las clases de servicio y stub deRouteGuide
para que los usen los clientes.
5. Implementa el servidor
Primero, veamos cómo crear un servidor RouteGuide
. Para que nuestro servicio de RouteGuide
funcione correctamente, se deben realizar dos pasos:
- Implementar la interfaz de servicio generada a partir de nuestra definición de servicio, que realiza el "trabajo" real de nuestro servicio
- Ejecutar un servidor de gRPC para escuchar las solicitudes de los clientes y enviarlas a la implementación del servicio correcta
Implementa RouteGuide
Como puedes ver, nuestro servidor tiene una clase RouteGuideService
que extiende la clase abstracta RouteGuideGrpc.RouteGuideImplBase
generada:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
Proporcionamos los siguientes 2 archivos para inicializar tu servidor con funciones:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
Veamos en detalle una implementación simple de RPC.
RPC unaria
RouteGuideService
implementa todos nuestros métodos de servicio. En este caso, solo es GetFeature()
, toma un mensaje Point
del cliente y devuelve en un mensaje Feature
la información de ubicación correspondiente de una lista de lugares conocidos.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
El método getFeature()
toma dos parámetros:
Point
: Es la solicitud.StreamObserver<Feature>
: Es un observador de respuestas, que es una interfaz especial para que el servidor llame con su respuesta.
Para devolver nuestra respuesta al cliente y completar la llamada, haz lo siguiente:
- Construimos y completamos un objeto de respuesta
Feature
para devolverlo al cliente, como se especifica en nuestra definición de servicio. En este ejemplo, lo hacemos en un métodocheckFeature()
privado independiente. - Usamos el método
onNext()
del observador de respuestas para devolver elFeature
. - Usamos el método
onCompleted()
del observador de respuestas para especificar que terminamos de controlar la RPC.
Inicia el servidor
Una vez que implementamos todos los métodos de servicio, debemos iniciar un servidor de gRPC para que los clientes puedan usar nuestro servicio. Incluimos en nuestra plantilla la creación del objeto ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
Creamos el servicio en el constructor:
- Especifica el puerto que queremos usar para escuchar las solicitudes del cliente con el método
forPort()
del compilador (usará la dirección comodín). - Crea una instancia de nuestra clase de implementación de servicio
RouteGuideService
y pásala al métodoaddService()
del compilador. - Llama a
build()
en el compilador para crear un servidor RPC para nuestro servicio.
En el siguiente fragmento, se muestra cómo creamos un 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));
}
En el siguiente fragmento, se muestra cómo creamos un objeto de servidor para nuestro servicio 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();
}
Implementa un método de inicio que llame a start
en el servidor que creamos anteriormente.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Implementa un método para esperar a que se complete el servidor, de modo que no salga de inmediato.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
Como puedes ver, compilamos e iniciamos nuestro servidor con un ServerBuilder
.
En el método principal, hacemos lo siguiente:
- Crea una instancia de
RouteGuideServer
. - Llama a
start()
para activar un servidor RPC para nuestro servicio. - Espera a que se detenga el servicio llamando a
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. Crea el cliente
En esta sección, veremos cómo crear un cliente para nuestro servicio RouteGuide
.
Crea una instancia de un stub
Para llamar a los métodos del servicio, primero debemos crear un stub. Existen dos tipos de stubs, pero solo necesitamos usar el de bloqueo para este codelab. Los dos tipos son los siguientes:
- Un stub síncrono/de bloqueo que realiza una llamada a RPC y espera a que responda el servidor, y que devolverá una respuesta o generará una excepción.
- Un stub asíncrono/sin bloqueo que realiza llamadas sin bloqueo al servidor, en el que la respuesta se devuelve de forma asíncrona. Solo puedes realizar ciertos tipos de llamadas de transmisión con el stub asíncrono.
Primero, debemos crear un canal de gRPC y, luego, usarlo para crear nuestro stub.
Podríamos haber usado un ManagedChannelBuilder
directamente para crear el canal.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
Pero usemos un método de utilidad que tome una cadena con hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
Ahora podemos usar el canal para crear nuestro stub de bloqueo. En este codelab, solo tenemos RPCs de bloqueo, por lo que usamos el método newBlockingStub
proporcionado en la clase RouteGuideGrpc
que generamos a partir de nuestro .proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
Llama a métodos de servicio
Ahora veamos cómo llamamos a nuestros métodos de servicio.
RPC simple
Llamar al RPC simple GetFeature
es casi tan sencillo como llamar a un método local.
Creamos y completamos un objeto de búfer de protocolo de solicitud (en nuestro caso, Point
), lo pasamos al método getFeature()
en nuestro stub de bloqueo y obtenemos un Feature
.
Si se produce un error, se codifica como un Status
, que podemos obtener del 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;
}
El código estándar registra un mensaje que contiene el contenido según si había o no una función en el punto especificado.
7. ¡Pruébalo!
- Dentro del directorio
start_here
, ejecuta el siguiente comando:
$ ./gradlew installDist
Esto compilará tu código, lo empaquetará en un archivo JAR y creará las secuencias de comandos que ejecutan el ejemplo. Se crearán en el directorio build/install/start_here/bin/
. Los guiones son route-guide-server
y route-guide-client
.
El servidor debe estar en ejecución antes de iniciar el cliente.
- Ejecuta el servidor:
$ ./build/install/start_here/bin/route-guide-server
- Ejecuta el cliente:
$ ./build/install/start_here/bin/route-guide-client
Verás un resultado como este, con marcas de tiempo omitidas para mayor claridad:
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. ¿Qué sigue?
- Obtén información sobre cómo funciona gRPC en Introducción a gRPC y Conceptos básicos
- Lee el instructivo sobre conceptos básicos.
- Explora la referencia de la API.