1-1-1 最佳實務

所有 proto 定義檔,每個檔案都應該只有一個頂層元素和建置目標。

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

  • 一個 proto_library 建置規則
  • 一個來源 .proto 檔案
  • 一個頂層實體 (message、enum 或 extension)

盡可能減少 message、enum、extension 和 service 的數量,可以讓重構更容易。當檔案分離時,移動檔案比從包含其他 message 的檔案中提取 message 容易得多。

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

在某些情況下,1-1-1 理想可能不可行 (循環依賴),不理想 (概念上極度耦合的 message 透過共置可以提高可讀性),或者某些缺點不適用 (當 .proto 檔案沒有 import 時,就不存在關於傳遞依賴大小的技術考量)。與任何最佳實務一樣,當需要偏離指南時,請運用良好的判斷力。

proto schema 檔案模組化很重要的一個地方是在建立 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);
}

service 定義和每個 message 定義都各自在自己的檔案中,並且您可以使用 include 來從其他 schema 檔案存取 message。

在此範例中,StudentStudentIdFullName 是跨請求和回應可重複使用的網域類型。頂層請求和回應 proto 對於每個 service+method 都是唯一的。

如果您稍後需要在 FullName message 中新增 middle_name 欄位,您將不需要使用該新欄位更新每個個別的頂層 message。同樣地,如果您需要使用更多資訊更新 Student,則所有請求和回應都會獲得更新。此外,StudentId 可能會更新為多部分 ID。

最後,即使將像 StudentId 這樣的簡單類型包裝成 message,也意味著您建立了一個具有語意和整合文件化的類型。對於像 FullName 這樣的類型,您需要小心 PII 記錄的位置;這是不在多個頂層 message 中重複這些欄位的另一個優點。您可以將這些欄位在一個地方標記為敏感,並將其從記錄中排除。