1-1-1 最佳實踐

所有 proto 定義應在每個檔案中具有一個頂級元素和一個構建目標。

“1-1-1”最佳實踐主張將定義組織為每個 .proto 檔案一個頂級實體(訊息、列舉或擴充套件),對應一個 proto_library 構建規則。這種方法促進了小型、模組化的 proto 定義。主要好處包括簡化重構,可能縮短構建時間,以及由於最小化傳遞依賴而減小二進位制檔案大小。

原理

1-1-1 最佳實踐是儘可能合理地保持每個 proto_library 和 .proto 檔案小巧,理想情況是

  • 一個 proto_library 構建規則
  • 一個源 .proto 檔案
  • 一個頂級實體(訊息、列舉或擴充套件)

擁有儘可能少的訊息、列舉、擴充套件和服務,可以使重構更容易。當檔案分離時,移動檔案比從一個包含其他訊息的檔案中提取訊息要容易得多。

遵循此實踐可以透過實際上減少傳遞依賴的大小來幫助縮短構建時間和減小二進位制檔案大小:當某些程式碼只需要使用一個列舉時,在 1-1-1 設計下,它只依賴於定義該列舉的 .proto 檔案,並避免意外地引入可能僅由同一檔案中定義的另一個訊息使用的大量傳遞依賴。

在某些情況下,1-1-1 的理想不可行(迴圈依賴)、不理想(概念上極度耦合的訊息,透過共置具有可讀性優勢),或者某些缺點不適用(當 .proto 檔案沒有匯入時,沒有關於傳遞依賴大小的技術問題)。與任何最佳實踐一樣,請在何時偏離指南時做出良好判斷。

proto 模式檔案的模組化在建立 gRPC 定義時很重要。以下 proto 檔案集顯示了模組化結構。

student_id.proto

edition = "2023";

package my.package;

message StudentId {
  string value = 1;
}

full_name.proto

edition = "2023";

package my.package;

message FullName {
  string family_name = 1;
  string given_name = 2;
}

student.proto

edition = "2023";

package my.package;

import "student_id.proto";
import "full_name.proto";

message Student {
  StudentId id = 1;
  FullName name = 2;
}

create_student_request.proto

edition = "2023";

package my.package;

import "full_name.proto";

message CreateStudentRequest {
  FullName name = 1;
}

create_student_response.proto

edition = "2023";

package my.package;

import "student.proto";

message CreateStudentResponse {
  Student student = 1;
}

get_student_request.proto

edition = "2023";

package my.package;

import "student_id.proto";

message GetStudentRequest {
  StudentId id = 1;
}

get_student_response.proto

edition = "2023";

package my.package;

import "student.proto";

message GetStudentResponse {
  Student student = 1;
}

student_service.proto

edition = "2023";

package my.package;

import "create_student_request.proto";
import "create_student_response.proto";
import "get_student_request.proto";
import "get_student_response.proto";

service StudentService {
  rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse);
  rpc GetStudent(GetStudentRequest) returns (GetStudentResponse);
}

服務定義和每個訊息定義都各自在一個檔案中,您可以使用 include 來訪問來自其他模式檔案的訊息。

在此示例中,StudentStudentIdFullName 是可在請求和響應中重複使用的領域型別。頂級請求和響應 protos 對於每個服務+方法都是唯一的。

如果以後您需要在 FullName 訊息中新增一個 middle_name 欄位,則無需使用該新欄位更新每個單獨的頂級訊息。同樣,如果您需要使用更多資訊更新 Student,則所有請求和響應都會獲得更新。此外,StudentId 可能會更新為多部分 ID。

最後,將即使是簡單的型別(如 StudentId)包裝為訊息,意味著您建立了一個具有語義和統一文件的型別。對於像 FullName 這樣的內容,您需要小心此 PII 的日誌記錄位置;這是不在多個頂級訊息中重複這些欄位的另一個優點。您可以將這些欄位在一個地方標記為敏感並將其從日誌記錄中排除。