Compila un servicio de gRPC con Java

1. Descripción general

gRPC es un marco de trabajo y un conjunto de herramientas de llamada de procedimiento remoto (RPC) independientes del lenguaje y la plataforma que se desarrollaron en Google. Te permite definir un servicio con búferes de protocolo, un conjunto de herramientas y un lenguaje de serialización binaria particularmente potentes. Luego, te permite generar stubs idiomáticos de cliente y servidor a partir de la definición de tu servicio en una variedad de lenguajes.

En este codelab, aprenderás a compilar un servicio basado en Java que expone una API con el framework de gRPC y, luego, escribirás un cliente para usar el stub generado del cliente de gRPC.

Qué aprenderás

  • El lenguaje de búferes de protocolo
  • Cómo implementar un servicio de gRPC con Java
  • Cómo implementar un cliente de gRPC con Java

¿Cómo usarás este instructivo?

Ler Leer y completar los ejercicios

¿Cómo calificarías tu experiencia con la compilación de apps de Node.js?

Principiante Intermedio Avanzado

¿Cómo calificarías tu experiencia con la compilación de apps en Go?

Principiante Intermedio Avanzado

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

  1. Accede a la consola de Cloud y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Recuerde el ID de proyecto, un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar los recursos de Google Cloud recursos.

Ejecutar este codelab no debería costar mucho, tal vez nada. Asegúrate de seguir las instrucciones de la sección “Realiza una limpieza”, en la que se aconseja cómo cerrar recursos para que no se te facture más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 300.

Google Cloud Shell

Si bien este codelab se puede operar desde tu computadora, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).

  1. Para activar Cloud Shell desde la consola de Cloud, solo haz clic en Activar Cloud Shella8460e837e9f5fda.png (el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).

b532b2f19ab85dda.png

Captura de pantalla del 14 de junio de 2017 a las 10.13.43 p.m. .png

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID:

gcloud auth list

Resultado del comando

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Resultado del comando

[core]
project = <PROJECT_ID>

Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:

gcloud config set project <PROJECT_ID>

Si no conoce su PROJECT_ID, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de la consola de Cloud:

2485e00c1223af09.png

Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resultado del comando

<PROJECT_ID>
  1. Establece la zona predeterminada y la configuración del proyecto.
gcloud config set compute/zone us-central1-f

Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.

3. Crea un servicio de gRPC

Crea un proyecto nuevo de Java con Maven:

$ mvn archetype:generate -DgroupId=com.example.grpc \
 -DartifactId=grpc-hello-server \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DinteractiveMode=false
$ cd grpc-hello-server

Cómo agregar un archivo de definición de gRPC

En gRPC, las cargas útiles de servicio (solicitud y respuesta) y las operaciones de servicio deben capturarse en un IDL (lenguaje de definición de interfaz). gRPC usa la sintaxis de Protobuffer 3 para definir las cargas útiles y las operaciones de los mensajes. Creemos un archivo .proto para un servicio de saludo simple con una solicitud de saludo y una respuesta de saludo.

Primero, crea un directorio proto nuevo para contener el archivo .proto nuevo:

$ mkdir -p src/main/proto

Luego, crea un nuevo archivo .proto src/main/proto/GreetingService.proto.

Puedes usar vim,nano, o emacs para editar el archivo:

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

// Request payload
message HelloRequest {
  // Each message attribute is strongly typed.
  // You also must assign a "tag" number.
  // Each tag number is unique within the message.
  string name = 1;

  // This defines a strongly typed list of String
  repeated string hobbies = 2;

  // There are many more basics types, like Enum, Map
  // See https://developers.google.com/protocol-buffers/docs/proto3
  // for more information.
}

message HelloResponse {
  string greeting = 1;
}

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // Define a RPC operation
  rpc greeting(HelloRequest) returns (HelloResponse);
}

Agrega dependencias y complementos de gRPC

Una vez que tengas la definición, podemos generar el stub del servidor y el stub del cliente a partir de este archivo. Deberás agregar los complementos y las dependencias de gRPC.

Primero, agrega las dependencias de gRPC a pom.xml:

pom.xml

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

Luego, agrega el complemento:

pom.xml

<project>
  ...
  <dependencies>
    ...
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.6.2</version>
      </extension>
    </extensions>
    <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.9.0:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.24.0:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Genera los stubs

Cuando compiles la aplicación, el complemento convertirá las definiciones de .proto en código Java.

$ mvn -DskipTests package

Para ver los archivos generados, haz lo siguiente:

$ find target/generated-sources

Implementa el servicio

Primero, crea una nueva clase GreetingServiceImpl que implementará la operación greeting:

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
  // HelloRequest has toString auto-generated.
    System.out.println(request);

    // You must use a builder to construct a new Protobuffer object
    GreetingServiceOuterClass.HelloResponse response = GreetingServiceOuterClass.HelloResponse.newBuilder()
      .setGreeting("Hello there, " + request.getName())
      .build();

    // Use responseObserver to send a single response back
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

Implementa el servidor

Por último, deberás iniciar un servidor para que escuche en un puerto y registre esta implementación del servicio. Edita la clase App y su método principal:

src/main/java/com/example/grpc/App.java

package com.example.grpc;

import io.grpc.*;

public class App
{
    public static void main( String[] args ) throws Exception
    {
      // Create a new server to listen on port 8080
      Server server = ServerBuilder.forPort(8080)
        .addService(new GreetingServiceImpl())
        .build();

      // Start the server
      server.start();

      // Server threads are running in the background.
      System.out.println("Server started");
      // Don't exit the main thread. Wait until server is terminated.
      server.awaitTermination();
    }
}

Por último, ejecuta el servidor:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

4. Cómo consumir el servicio

El generador ya generó todos los stubs del cliente. Para simplificar el lab, usaremos el mismo proyecto de Maven, pero agregaremos una nueva clase Client con un nuevo método principal.

Primero, haz clic en + para abrir una nueva sesión de Cloud Shell, de modo que no tengas que detener el servidor:

1ff0fda960b9adfc.png

En la nueva sesión, cambia al directorio grpc-hello-server:

$ cd grpc-hello-server

Luego, agrega la nueva clase Client:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      // Channel is the abstraction to connect to a service endpoint
      // Let's use plaintext communication because we don't have certs
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // It is up to the client to determine whether to block the call
      // Here we create a blocking stub, but an async stub,
      // or an async stub with Future are always possible.
      GreetingServiceGrpc.GreetingServiceBlockingStub stub = GreetingServiceGrpc.newBlockingStub(channel);
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Finally, make the call using the stub
      GreetingServiceOuterClass.HelloResponse response = 
        stub.greeting(request);

      System.out.println(response);

      // A Channel should be shutdown before stopping the process.
      channel.shutdownNow();
    }
}

Por último, ejecuta el cliente:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"

Eso es todo. Es fácil, ¿verdad?

5. Servicio de transmisión

Hay muchas más cosas que puedes probar. Por ejemplo, puedes crear un servicio de transmisión con facilidad simplemente agregando la palabra clave stream en el archivo .proto al parámetro de solicitud o respuesta, p. ej.,

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

...

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // MODIFY HERE: Update the return to streaming return.
  rpc greeting(HelloRequest) returns (stream HelloResponse);
}

Actualiza tu servidor para que envíe varias respuestas en lugar de solo una. Puedes hacerlo realizando varias llamadas a responseObserver.onNext(...):

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
 
    ...

    // Feel free to construct different responses if you'd like.
    responseObserver.onNext(response);
    responseObserver.onNext(response);
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

El cliente debe usar un stub asíncrono en lugar del stub de bloqueo. Actualiza el código del cliente y asegúrate de actualizar el tipo stub a GreetingServiceStub:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

// New import
import io.grpc.stub.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // Replace the previous synchronous code with asynchronous code.
      // This time use an async stub:
       GreetingServiceGrpc.GreetingServiceStub stub = GreetingServiceGrpc.newStub(channel);

      // Construct a request
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Make an Asynchronous call. Listen to responses w/ StreamObserver
      stub.greeting(request, new StreamObserver<GreetingServiceOuterClass.HelloResponse>() {
        public void onNext(GreetingServiceOuterClass.HelloResponse response) {
          System.out.println(response);
        }
        public void onError(Throwable t) {
        }
        public void onCompleted() {
          // Typically you'll shutdown the channel somewhere else.
          // But for the purpose of the lab, we are only making a single
          // request. We'll shutdown as soon as this request is done.
          channel.shutdownNow();
        }
      });
    }
}

Vuelve a compilar la aplicación:

$ mvn -DskipTests package

Reinicia el servidor y el cliente en sus propias sesiones de Cloud Shell.

Para iniciar el servidor, haz lo siguiente:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

Para iniciar el cliente, haz lo siguiente:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"

6. ¡Felicitaciones!

Temas abordados:

  • El lenguaje de búferes de protocolo
  • Cómo implementar un servidor de gRPC con Java
  • Cómo implementar un cliente de gRPC con Java

Próximos pasos:

Envíanos tus comentarios

  • Tómate un momento para completar nuestra breve encuesta