פיתוח שירות gRPC באמצעות Java

1. סקירה כללית

gRPC הוא framework וערכת כלים של Google לניהול מרחוק (RPC) ללא שפה. היא מאפשרת להגדיר שירות באמצעות מאגרי פרוטוקולים, ערכה של כלים ושפה בינאריים במיוחד לביצוע סריאליזציה. לאחר מכן ניתן ליצור stubs אידיומטיים של לקוח ושרת מהגדרת השירות במגוון שפות.

ב-Codelab הזה תלמדו איך לפתח שירות מבוסס Java שחושף API באמצעות framework של gRPC, ואז לכתוב לקוח שישתמש ב-stub בצד הלקוח שנוצר על ידי gRPC.

מה תלמדו

  • שפת מאגר הנתונים הזמני של הפרוטוקולים
  • איך מטמיעים שירות gRPC באמצעות Java
  • איך מטמיעים לקוח gRPC באמצעות Java

איך תשתמשו במדריך הזה?

לקריאה בלבד לקרוא אותו ולבצע את התרגילים

איזה דירוג מגיע לחוויה שלך עם בניית אפליקציות של Node.js?

מתחילים בינונית בקיאים

איזה דירוג מגיע לחוויה שלך עם הפיתוח של אפליקציות Go?

מתחילים בינונית בקיאים

2. הגדרה ודרישות

הגדרת סביבה בקצב עצמאי

  1. נכנסים למסוף Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

חשוב לזכור את מזהה הפרויקט, שם ייחודי לכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא מתאים לכם, סליחה). בהמשך ב-Codelab הזה, היא תיקרא PROJECT_ID.

  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים של Google Cloud.

מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. חשוב לבצע את כל ההוראות בקטע 'ניקוי' שמסביר איך להשבית משאבים כדי שלא תצברו חיובים מעבר למדריך הזה. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

Google Cloud Shell

אומנם אפשר להפעיל את ה-Codelab הזה מהמחשב, אבל ב-Codelab הזה נשתמש ב-Google Cloud Shell, סביבת שורת הפקודה (CLI) שפועלת ב-Cloud.

המכונה הווירטואלית הזו שמבוססת על Debian נטענת עם כל הכלים למפתחים שדרושים לכם. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. כלומר, כל מה שדרוש ל-Codelab הזה הוא דפדפן (כן, הוא פועל ב-Chromebook).

  1. כדי להפעיל את Cloud Shell ממסוף Cloud, לוחצים על Activate Cloud Shell a8460e837e9f5fda.png (ההקצאה וההתחברות לסביבה אמורות להימשך כמה דקות).

b532b2f19ab85dda.png

צילום מסך מתאריך 2017-06-14 בשעה 22:13.43.png

אחרי ההתחברות ל-Cloud Shell, אתם אמורים לראות שכבר בוצע אימות ושהפרויקט כבר מוגדר ל-PROJECT_ID שלכם.

gcloud auth list

פלט הפקודה

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

פלט הפקודה

[core]
project = <PROJECT_ID>

אם מסיבה כלשהי הפרויקט לא מוגדר, פשוט מריצים את הפקודה הבאה:

gcloud config set project <PROJECT_ID>

רוצה למצוא את ה-PROJECT_ID שלך? אתם יכולים לבדוק באיזה מזהה השתמשתם בשלבי ההגדרה או לחפש אותו במרכז הבקרה של מסוף Cloud:

2485e00c1223af09.png

Cloud Shell גם מגדירה משתני סביבה כברירת מחדל, והוא יכול להיות שימושי כשמריצים פקודות עתידיות.

echo $GOOGLE_CLOUD_PROJECT

פלט הפקודה

<PROJECT_ID>
  1. בשלב האחרון, מגדירים את ברירת המחדל של האזור והפרויקט.
gcloud config set compute/zone us-central1-f

אפשר לבחור מגוון אזורים שונים. מידע נוסף זמין במאמר אזורים ו אזורים.

3. יצירת שירות gRPC

יוצרים פרויקט Java חדש באמצעות Maven:

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

הוספת קובץ הגדרה של gRPC

ב-gRPC, יש לתעד את מטענים ייעודיים (payloads) של שירות (בקשה ותגובה) ואת פעולות השירות ב-IDL (שפת הגדרת ממשק). ב-gRPC משתמשים בתחביר Protobuffer 3 כדי להגדיר מטענים ייעודיים של הודעות ופעולות. ניצור קובץ פרוטו לשירות ברכה פשוט עם בקשת שלום ותשובה שלום.

קודם כול, יוצרים ספריית Proto חדשה שתכיל את קובץ ה-proto החדש:

$ mkdir -p src/main/proto

לאחר מכן, יוצרים קובץ אב src/main/proto/GreetingService.proto חדש.

כדי לערוך את הקובץ, אפשר להשתמש ב-vim,nano, או ב-emacs:

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

הוספת יחסי תלות ופלאגין של gRPC

אחרי שמקבלים את ההגדרה, אפשר ליצור מהקובץ הזה גם את ה-stub בצד השרת וגם את ה-stub בצד הלקוח. צריך להוסיף את יחסי התלות ויישומי הפלאגין של gRPC.

קודם כול מוסיפים את יחסי התלות של gRPC אל 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>

לאחר מכן מוסיפים את הפלאגין:

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>

יצירת ה-Stubs

כשיוצרים את האפליקציה, הפלאגין ממיר את הגדרות הפרוטו לקוד Java.

$ mvn -DskipTests package

כדי להציג את הקבצים שנוצרו:

$ find target/generated-sources

הטמעת השירות

קודם כול צריך ליצור מחלקה חדשה של GreetingServiceImpl שתטמיע את הפעולה 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();
  }
}

הטמעת השרת

בשלב האחרון, צריך להפעיל שרת כדי להאזין ליציאה ולרשום את הטמעת השירות. עורכים את המחלקה App וזו השיטה הראשית:

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

לבסוף, מפעילים את השרת:

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

4. צריכת השירות

המחולל כבר יצר את כל ה-stubs בצד הלקוח. כדי לפשט את שיעור ה-Lab, נשתמש באותו פרויקט של Maven, אבל פשוט נוסיף מחלקה חדשה של Client בשיטה ראשית חדשה.

קודם לוחצים על + כדי לפתוח סשן חדש ב-Cloud Shell כך שלא תצטרכו לסיים את השרת:

1ff0fda960b9adfc.png

בסשן החדש, עוברים לספרייה grpc-hello-server:

$ cd grpc-hello-server

לאחר מכן צריך להוסיף את הכיתה החדשה 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();
    }
}

לבסוף, מריצים את הלקוח:

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

זהו! די פשוט, נכון?

5. שירות סטרימינג

יש עוד הרבה דברים שאפשר לנסות. לדוגמה, כדי ליצור שירות סטרימינג בקלות, אפשר פשוט להוסיף את מילת המפתח stream בקובץ ה-Proto אל הבקשה או לפרמטר של התגובה, למשל

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

צריך לעדכן את השרת כך שישלח כמה תשובות במקום רק תשובה אחת. כדי לעשות זאת, אפשר לבצע כמה קריאות ל-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();
  }
}

הלקוח חייב להשתמש ב-stub אסינכרוני במקום ב-stub של החסימה. מעדכנים את קוד הלקוח ומקפידים לעדכן את הסוג stub ל-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();
        }
      });
    }
}

בונים מחדש את האפליקציה:

$ mvn -DskipTests package

מפעילים מחדש את השרת ואת הלקוח בסשן נפרד של Cloud Shell.

כדי להפעיל את השרת:

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

כדי להפעיל את אפליקציית הלקוח:

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

6. מעולה!

נושאים שטיפלנו בנושא:

  • שפת מאגר הנתונים הזמני של הפרוטוקולים
  • איך מטמיעים שרת gRPC באמצעות Java
  • איך מטמיעים לקוח gRPC באמצעות Java

השלבים הבאים:

נשמח לקבל ממך משוב

  • כדאי למלא את הסקר הקצר מאוד שלנו