بدء استخدام gRPC-Java

1. مقدمة

في هذا الدرس التطبيقي حول الترميز، ستستخدم gRPC-Java لإنشاء عميل وخادم يشكّلان الأساس لتطبيق يحدّد المسارات مكتوب بلغة Java.

في نهاية هذا الدليل التعليمي، سيكون لديك عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للمكان الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.

يتم تحديد واجهة برمجة التطبيقات للخادم في ملف بتنسيق Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نص نموذجي للعميل والخادم حتى يتمكّنا من التواصل مع بعضهما البعض، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.

لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.

ماذا ستتعلّم؟

  • كيفية استخدام "مخازن البروتوكولات المؤقتة" (Protocol Buffers) لتحديد واجهة برمجة تطبيقات الخدمة
  • كيفية إنشاء عميل وخادم يستندان إلى gRPC استنادًا إلى "مخازن البروتوكولات المؤقتة" من خلال إنشاء الرموز البرمجية آليًا
  • فهم عملية التواصل بين العميل والخادم باستخدام gRPC

هذا الدرس التطبيقي حول الترميز موجّه لمطوّري Java المبتدئين في gRPC أو الذين يريدون تجديد معلوماتهم في المجال، أو أي شخص آخر مهتم بتطوير أنظمة موزّعة. لا يُشترط توفّر خبرة سابقة في gRPC.

2. قبل البدء

المتطلبات الأساسية

  • الإصدار 8 من JDK أو إصدار أحدث

الحصول على الشفرة‏

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

أولاً، أنشئ دليل عمل الدرس التطبيقي وادخله:

mkdir grpc-java-getting-started && cd grpc-java-getting-started

نزِّل الدرس التطبيقي حول الترميز واستخرِجه:

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

بدلاً من ذلك، يمكنك تنزيل ملف ‎ .zip الذي يحتوي على دليل الدرس العملي فقط وفك ضغطه يدويًا.

يتوفّر الرمز المصدر المكتمل على GitHub إذا كنت تريد تخطّي كتابة عملية التنفيذ.

3. تحديد الخدمة

تتمثّل خطوتك الأولى في تحديد خدمة gRPC للتطبيق وطريقة استدعاء إجراء عن بُعد (RPC) وأنواع رسائل الطلبات والردود باستخدام مخازن البروتوكولات المؤقتة. ستوفّر خدمتك ما يلي:

  • طريقة استدعاء إجراء عن بُعد تُسمّى GetFeature ينفّذها الخادم ويستدعيها العميل.
  • نوعا الرسائل Point وFeature اللذان يمثّلان بنى البيانات المتبادلة بين العميل والخادم عند استخدام طريقة GetFeature يقدّم العميل إحداثيات الخريطة كـ Point في طلب GetFeature إلى الخادم، ويردّ الخادم بـ Feature مطابق يصف أي شيء يقع في تلك الإحداثيات.

سيتم تحديد طريقة "استدعاء الإجراء عن بُعد" هذه وأنواع الرسائل الخاصة بها في ملف src/main/proto/routeguide/route_guide.proto الخاص بالرمز المصدر المقدَّم.

يُشار إلى "مخازن البروتوكولات المؤقتة" عادةً باسم protobufs. لمزيد من المعلومات عن مصطلحات gRPC، يُرجى الاطّلاع على المفاهيم الأساسية والبنية ودورة الحياة في gRPC.

بما أنّنا ننشئ رمز Java في هذا المثال، حدّدنا خيار الملف java_package واسمًا لفئة Java في .proto:

option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";

أنواع الرسائل

في ملف routeguide/route_guide.proto الخاص بالرمز المصدر، حدِّد أولاً نوع رسالة Point. يمثّل Point زوج إحداثيات خط العرض وخط الطول على الخريطة. في هذا الدرس التطبيقي، استخدِم أعدادًا صحيحة للإحداثيات:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

الرقمان 1 و2 هما رقما تعريف فريدان لكل حقل من الحقول في بنية message.

بعد ذلك، حدِّد نوع رسالة Feature. يستخدم Feature الحقل string للاسم أو العنوان البريدي الخاص بمكان محدّد من خلال Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

طريقة الخدمة

يحتوي الملف route_guide.proto على بنية service باسم RouteGuide تحدّد طريقة واحدة أو أكثر توفّرها خدمة التطبيق.

أضِف GetFeature (وهي طريقة rpc) داخل تعريف RouteGuide. كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان مكان من مجموعة إحداثيات معيّنة، لذا اطلب من GetFeature عرض Feature لـ Point معيّن:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

هذه طريقة أحادية لاستدعاء الإجراء عن بُعد: استدعاء إجراء بسيط عن بُعد يرسل فيه العميل طلبًا إلى الخادم وينتظر تلقّي رد، تمامًا مثل استدعاء دالة محلية.

4. إنشاء رمز العميل والخادم

بعد ذلك، علينا إنشاء واجهات عميل وخادم gRPC من تعريف الخدمة .proto. وننفّذ ذلك باستخدام برنامج تجميع Protocol Buffers protoc مع مكوّن إضافي خاص بـ gRPC Java. عليك استخدام برنامج تجميع proto3 (الذي يتوافق مع بنية proto2 وproto3) لإنشاء خدمات gRPC.

عند استخدام Gradle أو Maven، يمكن لمكوّن protoc الإضافي للإنشاء إنشاء الرمز البرمجي اللازم كجزء من عملية الإنشاء. يمكنك الرجوع إلى ملف README الخاص بـ grpc-java لمعرفة كيفية إنشاء الرمز من ملفات .proto الخاصة بك.

لقد وفّرنا بيئة Gradle وإعداداتها في الرمز المصدري لبرنامج التدريب العملي هذا من أجل إنشاء هذا المشروع.

داخل دليل grpc-java-getting-started، نفِّذ الأمر التالي:

$ chmod +x gradlew
$ ./gradlew generateProto

يتم إنشاء الفئات التالية من تعريف الخدمة:

  • Feature.java وPoint.java وغيرها التي تحتوي على جميع رموز Protocol Buffers لتعبئة أنواع رسائل الطلبات والردود ونشرها على نحو متسلسِل واستردادها
  • RouteGuideGrpc.java الذي يحتوي (بالإضافة إلى بعض الرموز المفيدة الأخرى) على صنف أساسي للخوادم RouteGuide لتنفيذه، RouteGuideGrpc.RouteGuideImplBase، مع جميع الطرق المحددة في خدمة RouteGuide وفئات الرمز البديل التي يمكن للعملاء استخدامها.

5- تنفيذ الخادم

لنلقِ نظرة أولاً على كيفية إنشاء خادم RouteGuide. يتضمّن إنجاح خدمة RouteGuide جزأين:

  • تنفيذ واجهة الخدمة التي تم إنشاؤها من تعريف الخدمة، والتي تنفّذ "العمل" الفعلي لخدمتنا
  • تشغيل خادم gRPC للاستماع إلى الطلبات من العملاء وإرسالها إلى التنفيذ الصحيح للخدمة

تنفيذ RouteGuide

كما ترى، يحتوي الخادم على فئة RouteGuideService توسّع الفئة المجردة RouteGuideGrpc.RouteGuideImplBase التي تم إنشاؤها:

private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}

لقد قدّمنا الملفَّين التاليَين لتهيئة الخادم بالميزات:

./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java

./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json

لنلقِ نظرة على عملية تنفيذ بسيطة لبروتوكول RPC بالتفصيل.

استدعاء إجراء عن بُعد أحادي

تنفّذ RouteGuideService جميع طرق الخدمة لدينا. في هذه الحالة، تكون الدالة GetFeature() فقط، وتأخذ رسالة Point من العميل وتعرض في رسالة Feature معلومات المكان المطابقة من قائمة بالأماكن المعروفة.

@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
  responseObserver.onNext(checkFeature(request));
  responseObserver.onCompleted();
}

تتضمّن الطريقة getFeature() مَعلمتَين:

  • Point: الطلب
  • StreamObserver<Feature>: أداة مراقبة الاستجابة، وهي واجهة خاصة يمكن للخادم استخدامها للاتصال بها عند تلقّي استجابته.

لعرض ردّنا على العميل وإكمال المكالمة، اتّبِع الخطوات التالية:

  1. ننشئ عنصر استجابة Feature ونملأه لنعرضه على العميل، كما هو محدّد في تعريف الخدمة. في هذا المثال، نُجري ذلك في طريقة checkFeature() خاصة منفصلة.
  2. نستخدم طريقة onNext() في مراقب الاستجابة لعرض Feature.
  3. نستخدم طريقة onCompleted() في مراقب الاستجابة لتحديد أنّنا انتهينا من التعامل مع RPC.

بدء الخادم

بعد تنفيذ جميع طرق الخدمة، علينا بدء تشغيل خادم gRPC حتى يتمكّن العملاء من استخدام خدمتنا. نضمّن في نموذجنا الأوّلي إنشاء الكائن ServerBuilder:

ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)

ننشئ الخدمة في الدالة الإنشائية:

  1. حدِّد المنفذ الذي نريد استخدامه للاستماع إلى طلبات العميل باستخدام طريقة forPort() الخاصة بأداة الإنشاء (سيتم استخدام عنوان أحرف البدل).
  2. أنشئ مثيلاً لفئة تنفيذ الخدمة RouteGuideService ومرِّره إلى طريقة addService() الخاصة بأداة الإنشاء.
  3. استدعِ الدالة build() في أداة الإنشاء لإنشاء خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا.

يوضّح المقتطف التالي كيفية إنشاء عنصر 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));
  }

يوضّح المقتطف التالي كيفية إنشاء عنصر خادم لخدمة 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();
}

نفِّذ طريقة بدء تستدعي start على الخادم الذي أنشأناه أعلاه.

public void start() throws IOException {
  server.start();
  logger.info("Server started, listening on " + port);
}

نفِّذ طريقة لانتظار اكتمال الخادم حتى لا يخرج على الفور.

/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
  if (server != null) {
    server.awaitTermination();
  }
}

كما ترى، ننشئ خادمنا ونبدأ تشغيله باستخدام ServerBuilder.

في الطريقة الرئيسية، نقوم بما يلي:

  1. أنشئ مثيلاً من RouteGuideServer.
  2. اتّصِل على start() لتفعيل خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا.
  3. انتظِر إلى أن يتم إيقاف الخدمة من خلال الاتصال بالرقم blockUntilShutdown().
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

6. إنشاء العميل

في هذا القسم، سنلقي نظرة على كيفية إنشاء برنامج لخدمة RouteGuide.

إنشاء مثيل من رمز بديل

لاستدعاء طرق الخدمة، علينا أولاً إنشاء رمز بديل. هناك نوعان من الرموز البديلة، ولكننا نحتاج إلى استخدام الرمز البديل الحاصِر فقط في هذا الدرس التطبيقي حول الترميز. وهما:

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

علينا أولاً إنشاء قناة gRPC، ثم استخدام القناة لإنشاء الرمز البديل.

كان بإمكاننا استخدام ManagedChannelBuilder مباشرةً لإنشاء القناة.

ManagedChannelBuilder.forAddress(host, port).usePlaintext().build

ولكن لنستخدِم طريقة مساعدة تأخذ سلسلة تحتوي على hostname:port.

Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();

يمكننا الآن استخدام القناة لإنشاء رمز بديل للحظر. في هذا الدرس التطبيقي، لدينا طلبات RPC حظر فقط، لذا نستخدم طريقة newBlockingStub المتوفّرة في فئة RouteGuideGrpc التي أنشأناها من .proto.

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

طُرق خدمات الاستدعاء

لنلقِ نظرة الآن على كيفية استدعاء طرق الخدمة.

استدعاء إجراء بسيط عن بُعد

إنّ استدعاء إجراء بسيط GetFeature هو أمر مباشر تقريبًا مثل استدعاء طريقة محلية.

ننشئ كائنًا لمخزن Protocol Buffers المؤقت للطلب (في حالتنا Point) ونملأه، ثم نمرّره إلى الطريقة getFeature() في الرمز البديل الحظر، ونستردّ Feature.

في حال حدوث خطأ، يتم ترميزه على شكل Status، ويمكننا الحصول عليه من 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;
}

تسجّل الرموز النموذجية رسالة تتضمّن المحتوى استنادًا إلى ما إذا كانت هناك ميزة في النقطة المحدّدة أم لا.

7. ننصحكم بتجربتها.

  1. داخل دليل start_here، نفِّذ الأمر التالي:
$ ./gradlew installDist

سيؤدي ذلك إلى تجميع الرمز البرمجي وتعبئته في ملف jar وإنشاء البرامج النصية التي تشغّل المثال. سيتم إنشاؤها في دليل build/install/start_here/bin/. البرنامجان النصيان هما: route-guide-server وroute-guide-client.

يجب أن يكون الخادم قيد التشغيل قبل بدء تشغيل العميل.

  1. شغِّل الخادم:
$ ./build/install/start_here/bin/route-guide-server
  1. شغِّل العميل:
$ ./build/install/start_here/bin/route-guide-client

ستظهر لك نتيجة مشابهة لما يلي، مع حذف الطوابع الزمنية لتوضيح الصورة:

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. الخطوات التالية