غالبًا ما تقوم مكونات الاتصال بين الأنظمة التي تستخدم REST بتحويل محمولاتها إلى JSON. حتى الآن، لا يتمتع JSON بمعيار تحقق البنية الشائع الاستخدام: مخطط JSON ليس منتشرًا على نطاق واسع. التحقق القياسي من البنية يسمح بتفويض التحقق إلى مكتبة طرف ثالث والانتهاء منه. بدون واحد، يجب علينا الرجوع إلى التحقق اليدوي في الكود. أسوأ من ذلك، يجب أن نحتفظ برمز التحقق في تزامن مع البنية.
XML يتمتع بتحقق البنية مباشرة: مستند XML يمكن أن يعلن عن القواعد التي يجب أن يلتزم بها. SOAP، على أساس XML، يستفيد منه أيضًا.
بدائل التحويل الأخرى لديها خيار تحقق البنية: على سبيل المثال، Avro، Kryo، و Protocol Buffers. بشكل مثير للاهتمام، يستخدم gRPC Protobuf لتقديم RPC عبر المكونات الموزعة:
gRPC هو إطار عمل افتراضي مفتوح المصدر عالي الجودة لمبادرات الإدارة الفرعية (RPC) التي يمكن أن تعمل في أي بيئة. يمكن أن يربط بكفاءة خدمات في وعبر مراكز البيانات مع دعم قابل للتوصيل للموازنة الحمولية والتتبع وفحص الصحة والتوثيق. كما أنه ينطبق في الميل الأخير من الحوسبة الموزعة لربط الأجهزة والتطبيقات المحمولة والمتصفحات بالخدمات الخلفية.
علاوة على ذلك، البروتوكول هو آلية تحويل ثنائي للترميز، مما يوفر الكثير من حزمة البيانات. ومن هنا، يعتبر gRPC خيارًا ممتازًا للاتصال بين الأنظمة. ولكن إذا تحدثت جميع مكوناتك gRPC، كيف يمكن للعملاء البسيطين الاتصال بها؟ في هذا المنشور، سنبني خدمة gRPC ونوضح كيفية استدعائها من cURL.
A Simple gRPC Service
الوثائق gRPC شاملة، لذا هنا ملخص:
- gRPC هو إطار استدعاء إجراءات عن بعد.
- يعمل عبر مجموعة واسعة من لغات البرمجة.
- يعتمد على بروتوكول بافور:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
- إنه جزء من محفظة CNCF ويتم تكييفه �حاليًا.
دعونا نقوم بإعداد خدمة gRPC. سنستخدم Java، Kotlin، Spring Boot، ومشروع تكامل gRPC مخصص مع Spring Boot. تحتوي بنية المشروع على مشروعين: أحدهما للنموذج والآخر للكود. لنبدأ بمشروع النموذج.
I didn’t want something complicated. Reusing a simple example is enough: the request sends a string, and the response prefixes it with Hello
. We design this model in a dedicated Protobuf schema file:
syntax = "proto3"; //1
package ch.frankel.blog.grpc.model; //2
option java_multiple_files = true; //3
option java_package = "ch.frankel.blog.grpc.model"; //3
option java_outer_classname = "HelloProtos"; //3
service HelloService { //4
rpc SayHello (HelloRequest) returns (HelloResponse) {
}
}
message HelloRequest { //5
string name = 1; //6
}
message HelloResponse { //7
string message = 1; //6
}
- إصدار تعريف Protobuf
- الحزمة
- التكوين الخاص بـ Java
- تعريف الخدمة
- تعريف الطلب
- تعريف الحقل: يأتي أولاً النوع، ثم الاسم، وأخيرًا الترتيب.
- تعريف الاستجابة
سنستخدم Maven لتوليد رمز Java الشامل:
<project>
<dependencies>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId> <!--1-->
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId> <!--2-->
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId> <!--3-->
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- التبعيات في وقت التجميع
- استقبال معلومات عن نظام التشغيل؛ يستخدم في الإعداد التالي
- توليد رمز Java من ملف
proto
.
بعد التجميع، يجب أن يبدو الهيكل بشكل مماثل لما يلي:
يمكننا تعبئة الفئات في JAR واستخدامها في مشروع تطبيق الويب. الأخير مكتوب بلغة Kotlin، لكن هذا فقط لأنها اللغة المفضلة لدي في JVM.
نحتاج فقط إلى تبعية بدء Spring Boot محددة لدمج محاور gRPC مع Spring Boot:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
هذا هو الجزء المهم:
@GrpcService //1
class HelloService : HelloServiceImplBase() { //2
override fun sayHello(
request: HelloRequest, //2
observer: StreamObserver<HelloResponse> //3
) {
with(observer) {
val reply = HelloResponse.newBuilder() //2
.setMessage("Hello ${request.name}") //4
.build()
onNext(reply) //5
onCompleted() //5
}
}
}
- تكتشف
grpc-server-spring-boot-starter
التعليمات البرمجية التوجيهية وتبدأ في عملها. - الأصناف المرجعية المولدة في المشروع السابق
- تسمح توقيع الطريقة بمعامل
StreamObserver
. الفئة موجودة فيgrpc-stub.jar
. - احصل على الطلب وأضفه في البداية لبناء رسالة الاستجابة.
- شغل الأحداث.
يمكننا الآن بدء تشغيل تطبيق الويب باستخدام ./mvnw spring-boot:run
.
اختبار خدمة gRPC
الفكرة الكاملة وراء المشاركة هي أن الوصول إلى خدمة gRPC باستخدام أدوات عادية أمر مستحيل. للاختبار، نحتاج إلى أداة مخصصة بالرغم من ذلك. لقد وجدت grpcurl. لنقم بتثبيته واستخدامه لعرض الخدمات المتاحة:
grpcurl --plaintext localhost:9090 list #1-2
- انظر إلى جميع خدمات gRPC المتاحة بدون تحقق TLS.
- لتجنب التضارب بين gRPC وقنوات أخرى، مثل REST، يستخدم Spring Boot منفذًا آخر.
ch.frankel.blog.grpc.model.HelloService #1
grpc.health.v1.Health #2
grpc.reflection.v1alpha.ServerReflection #2
- خدمة gRPC التي حددناها
- خدمتان إضافيتان مقدمتان من البدء المخصص
يمكننا أيضًا الغوص في بنية الخدمة:
grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
service HelloService {
rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}
أخيرًا، يمكننا استدعاء الخدمة بالبيانات:
grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
{
"message": "Hello John"
}
الوصول إلى خدمة gRPC باستخدام أدوات عادية
تخيل أن لدينا تطبيق على الجانب العميل في JavaScript عادي يحتاج إلى الوصول إلى خدمة gRPC. ما هي البدائل؟
الأسلوب العام هو من خلال grpc-web
:
A JavaScript implementation of gRPC for browser clients. For more information, including a quick start, see the gRPC-web documentation.
يتصل عملاء gRPC-web بخدمات gRPC عبر مكبر خاص؛ بشكل افتراضي، يستخدم gRPC-web Envoy.
في المستقبل، نتوقع دعم gRPC-web في إطارات الويب الخاصة باللغات للغات مثل Python و Java و Node. للتفاصيل، انظر طريقة الطرح.
– grpc-web
يذكر الوصف قيودًا واحدة: يعمل فقط لبرمجة JavaScript (حتى الآن). ومع ذلك، هناك قيد آخر. إنه غير عادي للغاية. تحتاج إلى الحصول على ملف proto
، توليد التعليمات البرمجية الشبيهة بالقالب، وجعل التعليمات البرمجية الخاصة بك تستدعيها. يجب أن تفعل ذلك لكل نوع عميل. أسوأ من ذلك، إذا تغير ملف proto، يجب إعادة توليد رمز العميل في كل منهم.
هناك بديل، على الرغم من ذلك، إذا كنت تستخدم بوابة API. سأصف كيفية القيام بذلك مع Apache APISIX، ولكن ربما يمكن لبوابات أخرى أن تفعل الشيء نفسه. grpc-transcode هو إضافة تسمح بتحويل المكالمات REST إلى gRPC والعودة مرة أخرى.
الخطوة الأولى هي تسجيل ملف proto في Apache APISIX:
curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"
الخطوة الثانية هي إنشاء مسار باستخدام البرنامج النصي المذكور:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello", #1
"plugins": {
"grpc-transcode": {
"proto_id": "1", #2
"service": "ch.frankel.blog.grpc.model.HelloService", #3
"method": "SayHello" #4
}
},
"upstream": {
"scheme": "grpc",
"nodes": {
"server:9090": 1
}
}
}'
- تحديد مسار دقيق.
- إشارة إلى ملف بروتو المحدد في الأمر السابق.
- خدمة gRPC
- طريقة gRPC
عند هذه النقطة، أي عميل يمكنه إرسال طلب HTTP إلى النقطة المحددة. سيقوم أباتشي أبيسيك بترجمة المكالمة إلى gRPC، وتوجيهها إلى الخدمة المحددة، والحصول على الاستجابة، وترجمتها مرة أخرى.
curl localhost:9080/helloservice/sayhello?name=John
{"message":"Hello John"}
مقارنةً بـ grpc-web
، يتيح النهج بواسطة ممرر الطلبات المشاركة الملف proto
مع مكون واحد فقط: الممر الذي يمرر الطلبات نفسه.
فوائد الترجمة
عند هذه النقطة، يمكننا الاستفادة من قدرات ممرر الطلبات. تخيل أننا نرغب في قيمة افتراضية إذا لم يتم تمرير name
، على سبيل المثال، World
. سيكون المطورون سعداء في تعيينها في الكود، ولكن أي تغيير في القيمة سيتطلب بناءً كاملًا ونشرًا. يمكن أن تكون التغييرات تقريبًا فورية إذا وضعنا القيمة الافتراضية في سلسلة معالجة المسارات في الممر الذي يمرر الطلبات. دعونا نغير مسارنا وفقًا لذلك:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello",
"plugins": {
"grpc-transcode": {
...
},
"serverless-pre-function": { #1
"phase": "rewrite", #2
"functions" : [
"return function(conf, ctx) #3
local core = require(\"apisix.core\")
if not ngx.var.arg_name then
local uri_args = core.request.get_uri_args(ctx)
uri_args.name = \"World\"
ngx.req.set_uri_args(uri_args)
end
end"
]
}
},
"upstream": {
...
}
}'
- برنامج نمطي للأغراض العامة عندما لا يناسب أي منها
- إعادة كتابة الطلب.
- رمز لوا السحري الذي يفعل الخدعة
الآن، يمكننا تنفيذ الطلب بوساطة وسيط فارغ والحصول على النتيجة المتوقعة:
curl localhost:9080/helloservice/sayhello?name
{"message":"Hello World"}
الخاتمة
في هذا المنشور، قدمنا بإيجاز وصفًا لـ gRPC وكيف يفيد التواصل بين الخدمات. قمنا بتطوير خدمة gRPC بسيطة باستخدام Spring Boot و grpc-server-spring-boot-starter
. ومع ذلك، هناك تكلفة: لا يمكن للعملاء العاديين الوصول إلى الخدمة. اضطررنا إلى اللجوء إلى grpcurl
لاختبارها. الأمر سيء للعملاء المبني على JavaScript – أو المتصفح.
للتغلب على هذه القيود، يمكننا توجيه منصة API. قمت بعرض كيفية تكوين Apache APISIX مع إعدادات grpc-transcode
لتحقيق النتيجة المرغوبة.
يمكن العثور على الكود المصدري الكامل لهذا المنشور على GitHub.