Creazione di un servizio gRPC con Java

1. Panoramica

gRPC è un framework e un insieme di strumenti per chiamate di procedura remota (RPC) indipendenti dal linguaggio e dalla piattaforma, sviluppati da Google. Consente di definire un servizio utilizzando Protocol Buffers, un linguaggio e un insieme di strumenti di serializzazione binaria particolarmente potenti. Ti consente poi di generare stub client e server idiomatici dalla definizione del servizio in una serie di lingue.

In questo codelab imparerai a creare un servizio basato su Java che espone un'API utilizzando il framework gRPC e poi a scrivere un client per utilizzare lo stub lato client gRPC generato.

Cosa imparerai a fare

  • Il linguaggio di definizione dei protocolli
  • Come implementare un servizio gRPC utilizzando Java
  • Come implementare un client gRPC utilizzando Java

Come utilizzerai questo tutorial?

Leggilo e basta Leggilo e completa gli esercizi

Come valuti la tua esperienza di creazione di app Node.js?

Principiante Intermedio Avanzato

Come valuti la tua esperienza di creazione di app Go?

Principiante Intermedio Avanzato

2. Configurazione e requisiti

Configurazione dell'ambiente autonomo

  1. Accedi alla console Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Ricorda l'ID progetto, un nome univoco tra tutti i progetti Google Cloud (il nome sopra è già stato utilizzato e non funzionerà per te, mi dispiace). In questo codelab verrà chiamato PROJECT_ID.

  1. Successivamente, dovrai abilitare la fatturazione in Cloud Console per utilizzare le risorse Google Cloud.

L'esecuzione di questo codelab non dovrebbe costare molto, se non nulla. Assicurati di seguire le istruzioni riportate nella sezione "Pulizia", che ti consiglia come arrestare le risorse in modo da non incorrere in addebiti oltre questo tutorial. I nuovi utenti di Google Cloud possono beneficiare del programma prova senza costi di 300$.

Google Cloud Shell

Sebbene questo codelab possa essere utilizzato dal tuo computer, in questo codelab utilizzeremo Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.

Questa macchina virtuale basata su Debian viene caricata con tutti gli strumenti di sviluppo di cui avrai bisogno. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Ciò significa che per questo codelab ti servirà solo un browser (sì, funziona su Chromebook).

  1. Per attivare Cloud Shell dalla console Cloud, fai clic su Attiva Cloud Shell a8460e837e9f5fda.png (bastano pochi istanti per eseguire il provisioning e connettersi all'ambiente).

b532b2f19ab85dda.png

Screen Shot 2017-06-14 at 10.13.43 PM.png

Una volta eseguita la connessione a Cloud Shell, dovresti vedere che il tuo account è già autenticato e il progetto è già impostato sul tuo PROJECT_ID.

gcloud auth list

Output comando

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

Output comando

[core]
project = <PROJECT_ID>

Se per qualche motivo il progetto non è impostato, esegui questo comando:

gcloud config set project <PROJECT_ID>

Stai cercando PROJECT_ID? Controlla l'ID che hai utilizzato nei passaggi di configurazione o cercalo nella dashboard della console Cloud:

2485e00c1223af09.png

Cloud Shell imposta anche alcune variabili di ambiente per impostazione predefinita, che potrebbero essere utili quando esegui i comandi futuri.

echo $GOOGLE_CLOUD_PROJECT

Output comando

<PROJECT_ID>
  1. Infine, imposta la zona e la configurazione del progetto predefinite.
gcloud config set compute/zone us-central1-f

Puoi scegliere una serie di zone diverse. Per saperne di più, consulta Regioni e zone.

3. Crea un servizio gRPC

Crea un nuovo progetto Java con Maven:

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

Aggiungere un file di definizione gRPC

In gRPC, i payload del servizio (richiesta e risposta) e le operazioni del servizio devono essere acquisiti in un IDL (Interface Definition Language). gRPC utilizza la sintassi di Protobuffer 3 per definire i payload e le operazioni dei messaggi. Creiamo un file proto per un semplice servizio di saluto con una richiesta di saluto e una risposta di saluto.

Innanzitutto, crea una nuova directory proto per contenere il nuovo file proto:

$ mkdir -p src/main/proto

Poi, crea un nuovo file proto src/main/proto/GreetingService.proto.

Puoi utilizzare vim,nano, o emacs per modificare il file:

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);
}

Aggiungere dipendenze e plug-in gRPC

Una volta ottenuta la definizione, possiamo generare sia lo stub lato server sia quello lato client da questo file. Dovrai aggiungere i plug-in e le dipendenze gRPC.

Innanzitutto, aggiungi le dipendenze 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>

Quindi, aggiungi il plug-in:

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>

Generare gli stub

Quando crei l'applicazione, il plug-in converte le definizioni proto in codice Java.

$ mvn -DskipTests package

Per visualizzare i file generati:

$ find target/generated-sources

Implementare il Servizio

Innanzitutto, crea una nuova classe GreetingServiceImpl che implementerà l'operazione 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();
  }
}

Implementare il server

Infine, dovrai avviare un server per ascoltare una porta e registrare questa implementazione del servizio. Modifica la classe App e il relativo metodo principale:

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();
    }
}

Infine, esegui il server:

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

4. Utilizzo del servizio

Il generatore ha già generato tutti gli stub lato client. Per semplificare il lab, utilizzeremo lo stesso progetto Maven, ma aggiungeremo semplicemente una nuova classe Client con un nuovo metodo principale.

Innanzitutto, fai clic su + per aprire una nuova sessione di Cloud Shell in modo da non dover terminare il server:

1ff0fda960b9adfc.png

Nella nuova sessione, passa alla directory grpc-hello-server:

$ cd grpc-hello-server

Quindi, aggiungi la nuova classe 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();
    }
}

Infine, esegui il client:

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

È tutto. È un gioco da ragazzi, no?

5. Servizio di streaming

Puoi provare molte altre cose. Ad esempio, puoi creare facilmente un servizio di streaming semplicemente aggiungendo la parola chiave stream al parametro di richiesta o risposta nel file proto, ad esempio

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);
}

Aggiorna il server in modo che invii più risposte anziché una sola. Puoi farlo effettuando più chiamate 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();
  }
}

Il client deve utilizzare uno stub asincrono anziché uno stub di blocco. Aggiorna il codice client assicurandoti di aggiornare il 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();
        }
      });
    }
}

Ricrea l'applicazione:

$ mvn -DskipTests package

Riavvia sia il server che il client in una sessione Cloud Shell separata.

Per avviare il server:

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

Per avviare il client:

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

6. Complimenti!

Argomenti trattati:

  • Il linguaggio di definizione dei protocolli
  • Come implementare un server gRPC utilizzando Java
  • Come implementare un client gRPC utilizzando Java

Passaggi successivi:

Inviaci il tuo feedback

  • Ti invitiamo a dedicare qualche istante per completare il nostro brevissimo sondaggio.