إنشاء خدمة gRPC باستخدام Java

1. نظرة عامة

gRPC هي مجموعة أدوات وإطار عمل لاستدعاء الإجراء عن بُعد (RPC) لا يعتمد على لغة أو نظام أساسي، وقد تم تطويرها في Google. تتيح لك تحديد خدمة باستخدام "مخازن البروتوكولات المؤقتة"، وهي مجموعة أدوات ولغة تسلسل ثنائي فعّالة بشكل خاص. بعد ذلك، تتيح لك إنشاء نماذج أولية للعملاء والخوادم بلغات مختلفة من تعريف الخدمة.

في هذا الدرس التطبيقي حول الترميز، ستتعلّم كيفية إنشاء خدمة مستندة إلى Java تعرض واجهة برمجة تطبيقات باستخدام إطار عمل gRPC، ثم كتابة برنامج عميل لاستخدام رمز gRPC النموذجي الذي تم إنشاؤه من جهة العميل.

ما ستتعلمه

  • لغة Protocol Buffer
  • كيفية تنفيذ خدمة gRPC باستخدام Java
  • كيفية تنفيذ برنامج gRPC باستخدام Java

كيف ستستخدم هذا البرنامج التعليمي؟

قراءة المحتوى فقط قراءة المحتوى وإكمال التمارين

كيف تقيّم تجربتك في إنشاء تطبيقات Node.js؟

مبتدئ متوسط متمكّن

كيف تقيّم تجربتك في إنشاء تطبيقات Go؟

مبتدئ متوسط متقدّم

2. الإعداد والمتطلبات

إعداد البيئة بالسرعة التي تناسبك

  1. سجِّل الدخول إلى Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

تذكَّر رقم تعريف المشروع، وهو اسم فريد في جميع مشاريع Google Cloud (الاسم أعلاه مستخدَم حاليًا ولن يكون متاحًا لك، نأسف لذلك). سيتم الإشارة إليه لاحقًا في هذا الدرس العملي باسم PROJECT_ID.

  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console من أجل استخدام موارد Google Cloud.

لن تكلفك تجربة هذا الدرس التطبيقي حول الترميز الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. احرص على اتّباع أي تعليمات في قسم "التنظيف" الذي ينصحك بكيفية إيقاف الموارد حتى لا تتحمّل رسومًا تتجاوز هذا البرنامج التعليمي. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.

Google Cloud Shell

على الرغم من إمكانية تشغيل هذا الدرس العملي من جهاز الكمبيوتر، سنستخدم في هذا الدرس Google Cloud Shell، وهي بيئة سطر أوامر تعمل في السحابة الإلكترونية.

يتم تحميل هذا الجهاز الافتراضي المستند إلى Debian بجميع أدوات التطوير التي تحتاج إليها. توفّر هذه الخدمة دليلًا رئيسيًا دائمًا بسعة 5 غيغابايت وتعمل في Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. وهذا يعني أنّ كل ما تحتاجه لهذا الدرس التطبيقي حول الترميز هو متصفّح (نعم، يمكن استخدامه على جهاز Chromebook).

  1. لتفعيل Cloud Shell من Cloud Console، ما عليك سوى النقر على تفعيل Cloud Shell a8460e837e9f5fda.png (يستغرق توفير البيئة والاتصال بها بضع لحظات فقط).

b532b2f19ab85dda.png

Screen Shot 2017-06-14 at 10.13.43 PM.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 Console:

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، يجب تسجيل حمولات الخدمة (الطلبات والاستجابات) وعمليات الخدمة في IDL (لغة تعريف الواجهة). تستخدم gRPC بنية Protobuffer 3 لتحديد حمولات الرسائل والعمليات. لننشئ ملف proto لخدمة Greeting بسيطة تتضمّن طلب Hello ورد Hello.

أولاً، أنشئ دليل proto جديدًا للاحتفاظ بملف proto الجديد:

$ mkdir -p src/main/proto

بعد ذلك، أنشئ ملف 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

بعد الحصول على التعريف، يمكننا إنشاء كل من الرمز البديل من جهة الخادم والرمز البديل من جهة العميل من هذا الملف. عليك إضافة التبعيات والمكوّنات الإضافية لـ 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>

إنشاء رموز التخزين

عند إنشاء التطبيق، سيحوّل المكوّن الإضافي تعريفات proto إلى رمز 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. استخدام الخدمة

أنشأ المولد جميع رموز التخزين المؤقت من جهة العميل. لتسهيل عملية التدريب العملي، سنستخدم مشروع 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 إلى 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. تهانينا!

المواضيع التي تناولناها:

  • لغة Protocol Buffer
  • كيفية تنفيذ خادم gRPC باستخدام Java
  • كيفية تنفيذ برنامج gRPC باستخدام Java

الخطوات التالية:

تقديم ملاحظاتك

  • يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.