הגדרת שירות gRPC

כדי ליצור שירות gRPC – בין אם משתמשים ב-API Gateway ובין אם לא – צריך לציין את הגדרת הממשק באחד או יותר קובצי proto, שהם קובצי טקסט עם הסיומת .proto. בקובץ proto מגדירים את הממשק של ה-API, כולל מבני הנתונים, השיטות, הפרמטרים של השיטות וסוגי ההחזרה. לאחר מכן, קומפלו את קובץ proto באמצעות מקמפל מאגר אחסון לפרוטוקולים ספציפי לשפה, protoc. מידע נוסף זמין במאמרים מה זה gRPC? ומושגים של gRPC.

כדי ש-API Gateway ינהל את שירות ה-gRPC, בנוסף לקובץ ה-proto המהודר, צריך לציין הגדרת שירות בקובץ YAML אחד או יותר. הגדרת שירות היא מפרט שמאפשר להגדיר את ההתנהגות של שירות gRPC, כולל אימות, מכסות ועוד.

סקירה כללית על הגדרת שירות

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

לדוגמה:

type: google.api.Service
config_version: 3
name: calendar.googleapis.com
title: Google Calendar API
apis:
- name: google.calendar.v3.Calendar
authentication:
  providers:
  - id: google_calendar_auth
    jwks_uri: https://www.googleapis.com/oauth2/v1/certs
    issuer: https://securetoken.google.com
  rules:
  - selector: "*"
    requirements:
      provider_id: google_calendar_auth
backend:
  rules:
    - selector: "*"
      address: grpcs://my-service-98sdf8sd0-uc.a.run.app

כל קטע משנה בדרך כלל תואם להודעת .proto שבה מגדירים היבטים שונים של השירות. לדוגמה:

  • authentication: מציין איך מתבצע אימות של המתקשרים.
  • backend: שולט בניתוב של ה-backend המרוחק.
  • http: מגדיר את הכללים למיפוי של method ל-RPC ל-method אחת או יותר של API בארכיטקטורת REST.
  • usage: מאפשר להפעיל ולהשבית אימות של מפתח API באופן סלקטיבי.

הסכימה הרשמית של הגדרת השירות מוגדרת על ידי ההודעה .proto google.api.Service.

הגדרה בסיסית

הדוגמה של חנות הספרים, שמשמשת במדריך תחילת העבודה עם API Gateway ו-Cloud Run ל-gRPC, כוללת קובץ הגדרת ממשק בסיסי וקובץ הגדרת שירות.

הגדרת הממשק של חנות הספרים, bookstore.proto:

syntax = "proto3";

package endpoints.examples.bookstore;

option java_multiple_files = true;
option java_outer_classname = "BookstoreProto";
option java_package = "com.google.endpoints.examples.bookstore";

import "google/protobuf/empty.proto";

service Bookstore {
  rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {}
  rpc CreateShelf(CreateShelfRequest) returns (Shelf) {}
  rpc GetShelf(GetShelfRequest) returns (Shelf) {}
  rpc DeleteShelf(DeleteShelfRequest) returns (google.protobuf.Empty) {}
  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}
  rpc CreateBook(CreateBookRequest) returns (Book) {}
  rpc GetBook(GetBookRequest) returns (Book) {}
  rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {}
}

message Shelf {
  int64 id = 1;
  string theme = 2;
}

message Book {
  int64 id = 1;
  string author = 2;
  string title = 3;
}

הגדרת השירות של חנות הספרים, api_config.yaml:

type: google.api.Service
config_version: 3

name: *.apigateway.PROJECT_ID.cloud.goog

title: Bookstore gRPC API
apis:
- name: endpoints.examples.bookstore.Bookstore

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

  • הגדרת שירות בשם *.apigateway.PROJECT_ID.cloud.goog, כאשר PROJECT_ID הוא שם מזהה הפרויקט ב- Google Cloud .
  • ההגדרה הזו מציינת שהשירות חושף את ה-API‏ endpoints.examples.bookstore.Bookstore, כפי שמוגדר בקובץ bookstore.proto.

כללים ובוררים

במקרים מסוימים, יכול להיות שתצטרכו לשייך הגדרה לרכיבים ספציפיים בשירות – לדוגמה, כדי לאכוף אימות בשיטות מסוימות אבל לא בשיטות אחרות. כדי להגדיר את זה בהגדרות השירות, בחלקים מסוימים של הגדרות השירות, כמו authentication ו-http, אפשר לציין כללים ובוררים. סלקטור מזהה את הרכיבים של השירות, כמו שם של שיטה שההגדרה שמשויכת לכלל חלה עליה.

בורר הוא רשימה מופרדת בפסיקים של שמות מלאים שמוגדרים בשירות: שיטה, הודעה, שדה, enum או ערכי enum. הפלח האחרון של השם יכול להיות התו הכללי לחיפוש *, שתואם לכל סיומת. אפשר להשתמש בתווים כלליים רק בסוף השם ורק עבור קטע שלם בשם. לדוגמה: foo.* תקין, אבל foo.b* או foo.*.bar לא תקינים. כדי להגדיר ברירת מחדל לכל הרכיבים הרלוונטיים, מציינים רק *.

דוגמה 1 (משפיעה על כל השירות):

usage:
  rules:
  # Allow unregistered calls for all methods.
  - selector: "*"
    allow_unregistered_calls: true

דוגמה 2 (משפיעה על שיטה אחת):

usage:
  rules: # IMPORTANT: ONLY LAST MATCHING RULE IS APPLIED
  # Disable API key validation on just the ListShelves method
  # while requiring an API key for the rest of the API.
  - selector: "*"
    allow_unregistered_calls: false
  - selector: "endpoints.examples.bookstore.BookStore.ListShelves"
    allow_unregistered_calls: true

בדוגמה הקודמת אפשר לראות איך להגדיר אימות של מפתח API לכל ה-methods, חוץ מ-method ‏ListShelves.

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

שימוש בכמה קובצי YAML

יכול להיות שיהיה לכם שימושי להשתמש ביותר מקובץ YAML אחד כדי להגדיר תכונות שונות לאותו שירות. שימוש בכמה קובצי YAML מקל על השימוש החוזר בקבצים ועל שינוי שלהם עבור סביבות שונות. לדוגמה, בדוגמה של חנות הספרים, ההגדרה הבסיסית מצוינת בקובץ api_config.yaml, והכללים של HTTP מצוינים בקובץ api_config_http.yaml. כך תוכלו לפרוס את כללי ה-HTTP רק אם אתם רוצים להפעיל המרת קידוד מ-JSON/HTTP ל-gRPC עבור חנות הספרים.

אם משתמשים בכמה קובצי YAML להגדרת השירות, כשמבצעים פריסה של ההגדרה, כל קובץ מומר להודעות פרוטו google.api.Service, ואז כל ההודעות ממוזגות באמצעות סמנטיקה של מיזוג פרוטו. כלומר, כל השדות הסקלריים היחידים במופע השני מחליפים את השדות במופע הראשון. לכן, אם תספקו ערכים שונים של יחיד לאותו כלל בשני קבצים, המערכת תשתמש בערך שבקובץ השני שתציינו כשפורסים את ההגדרה. הודעות מוטמעות יחידות ממוזגות, ושדות חוזרים מחוברים.

בדומה לכללים, המיזוג תלוי בסדר. אם יש שתי הגדרות שירות, הגדרת השירות השנייה מבטלת את הראשונה.

הערות (כללי HTTP בלבד)

במקום להשתמש בקובץ YAML כדי להגדיר אפשרויות HTTP, אפשר להגדיר אותן ישירות בקובץ proto באמצעות מנגנון האפשרויות. ההערות ל-API מוגדרות בקובץ annotations.proto.

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

אם אפשרות הגדרה מסופקת גם בקובץ proto וגם בקובץ ה-YAML של הגדרת השירות, הגדרת השירות מבטלת את ההערה.

הערה לדוגמה בקובץ proto:

rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {
    option (google.api.http) = { get: "/v1/shelves" };
}


# The preceding annotation is equivalent to the following service configuration:

http:
  rules:
  - selector: endpoints.examples.bookstore.BookStore.ListShelves
    get: '/v1/shelves'

מידע נוסף אפשר למצוא במאמר בנושא המרת קידוד HTTP/JSON ל-gRPC.

המאמרים הבאים