1-1-1 ベストプラクティス

すべての proto 定義は、ファイルごとに1つのトップレベル要素とビルドターゲットを持つ必要があります。

1-1-1 ベストプラクティスは、すべての proto_library ファイルと .proto ファイルを合理的に可能な限り小さく保つことです。理想は次のとおりです。

  • 1つの proto_library ビルドルール
  • 1つのソース .proto ファイル
  • 1つのトップレベルエンティティ (メッセージ、enum、または拡張機能)

メッセージ、enum、拡張機能、およびサービスの数を合理的に可能な限り少なくすることで、リファクタリングが容易になります。ファイルが分離されている場合、ファイルを移動する方が、他のメッセージを含むファイルからメッセージを抽出するよりもはるかに簡単です。

このプラクティスに従うことで、実際には推移的な依存関係のサイズを縮小することにより、ビルド時間とバイナリサイズを削減できます。一部のコードが1つの enum のみを使用する必要がある場合、1-1-1 設計の下では、その enum を定義する .proto ファイルのみに依存し、同じファイルで定義された別のメッセージでのみ使用される可能性のある大規模な推移的な依存関係のセットを誤って取り込むことを回避できます。

1-1-1 の理想が不可能 (循環依存関係)、理想的ではない (可読性の利点がある非常に概念的に結合されたメッセージが同じ場所に配置されている場合)、またはいくつかの欠点が適用されない場合 (.proto ファイルにインポートがない場合、推移的な依存関係のサイズに関する技術的な懸念はありません) があります。ベストプラクティスと同様に、ガイドラインから逸脱するタイミングについては適切な判断を使用してください。

proto スキーマファイルのモジュール性が重要な場所の1つは、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);
}

サービス定義と各メッセージ定義はそれぞれ独自のファイルにあり、インクルードを使用して他のスキーマファイルからのメッセージへのアクセスを提供します。

この例では、StudentStudentId、および FullName は、リクエストとレスポンス全体で再利用可能なドメインタイプです。トップレベルのリクエストおよびレスポンス proto は、各サービス+メソッドに固有です。

後で middle_name フィールドを FullName メッセージに追加する必要がある場合、その新しいフィールドですべての個々のトップレベルメッセージを更新する必要はありません。同様に、Student をより多くの情報で更新する必要がある場合、すべてのリクエストとレスポンスが更新を取得します。さらに、StudentId はマルチパート ID に更新される可能性があります。

最後に、StudentId のような単純な型でさえメッセージとしてラップすることは、セマンティクスと統合されたドキュメントを持つ型を作成したことを意味します。FullName のようなものについては、この PII がどこにログ記録されるかに注意する必要があります。これは、これらのフィールドを複数のトップレベルメッセージで繰り返さないことのもう1つの利点です。これらのフィールドを1か所で機密としてタグ付けし、ロギングから除外できます。