パートナーから gRPC サービスを受け取るとき、実際に渡されるのは .proto ファイルの入ったフォルダーです。それらのファイルが契約であり、その中のすべてのメッセージを Delphi のコードで読み書きできる形にしなければなりません。これを手作業で行うのは手間がかかるうえに間違えやすく、しかも相手側が契約の新しいバージョンを出すたびにやり直す必要があります。
sgcProtoBuf は、その作業を代わりに行うコードジェネレーターです。.proto ファイルを指定するだけで、メッセージごとにクラスを備えた Delphi ユニットが生成され、そのままシリアライズとデシリアライズを行えます。proto2 と proto3 の両方を読み取り、構文はファイル自体から判断します。ウィザードとコマンドラインツールの両方が提供されます。
ファイルを 1 つ渡せば、ツリー全体が手に入ります
実際の proto ツリーが 1 ファイルで済むことはありません。互いに import し合い、さらにその上に Google の定義を import します。典型的なサービスファイルは次のように始まります。
syntax = "proto3";
package acme.telemetry.v1;
import "acme/common/v1/device.proto";
import "google/protobuf/timestamp.proto";
message Reading {
string device_id = 1;
double value = 2;
google.protobuf.Timestamp taken_at = 3;
}
インポートを解決する を有効にしてそのファイルを 1 つ選択すると、sgcProtoBuf は import 文を再帰的にたどり、見つかったすべての依存関係を生成して、生成したユニットを uses 節に追加します。proto ツリーのルートフォルダーは選択したファイルから自動的に検出されるため、通常は設定するものは何もありません。
定義がディスク上に見つからない場合は、コンパイルできないユニットを黙って生成するのではなく、メッセージリストに報告されます。コンパイラーのエラーを追いかける代わりに、どのファイルが不足しているのかが正確にわかります。
ユニット名は package から決まります
生成されるユニットは package 文にもとづいて命名されます。つまり package acme.telemetry.v1; からは unit acme.telemetry.v1; が生成され、acme.telemetry.v1.pas として保存されます。ファイル名は常にユニット名と一致するため、避けられるはずのコンパイルエラーが 1 種類まるごとなくなります。
これは同時に、同じ package を宣言する複数の .proto ファイルが 1 つのユニットにまとまることも意味します。まさに契約の作成者が意図したとおりです。acme.common.v1 を宣言する device.proto、device_service.proto、units.proto が入ったフォルダーは、1 つの acme.common.v1.pas になります。
Google の型は最初から同梱されています
実際の proto ファイルのほとんどは google.protobuf から何かを import します。それらを生成する必要はありません。sgcWebSockets はすでに sgcProtoBuf_WellKnownTypes ユニットでそれらを提供しており、ジェネレーターが自動的にマッピングします。対象は Timestamp、Duration、Any、Empty、FieldMask、Struct、そして StringValue や Int32Value といったラッパー型です。
そのため、上記の google.protobuf.Timestamp フィールドは TsgcProtoBufTimestamp になり、ToDateTime と FromDateTime がすでに実装されています。Google の .proto ファイルを変換する必要はまったくありません。
enum を本物の Delphi 型として
既定では enum は定数のブロックとして生成されます。enum を Delphi の列挙型として生成する を有効にすると、代わりに正式な型が得られます。読み取りが短く済み、無関係な整数と取り違えることもありません。
CountryCodeDto = (
COUNTRY_CODE_DTO_NONE = 0,
COUNTRY_CODE_DTO_NL = 1,
COUNTRY_CODE_DTO_DE = 2,
COUNTRY_CODE_DTO_CH = 3
);
ここには、知っておく価値のある正直な制限が 1 つあります。Protocol Buffers の enum には、Delphi の列挙型にはできないことができます。負の値を保持でき、2 つの名前を同じ番号にエイリアスでき、同じ package 内の 2 つの enum が同じ値名を宣言できます。ジェネレーターがそうしたケースに出会うと、その enum についてのみ定数形式にフォールバックし、その理由を説明するコメントを書き込みます。ファイル内の他の enum は影響を受けないため、可能な箇所では本物の型が得られ、それ以外の箇所でも動作するユニットが得られます。
生成されるコード
すべてのメッセージクラスは、生成された基底クラスを継承し、LoadFromBytes、LoadFromStream、ToBytes を公開して、フィールドごとにプロパティを持ちます。以下の例では proto の元の要素名を保持する オプションを使用しています。このオプションは Tsgc プレフィックスを付けず、契約に記載されているとおりの名前を維持します。
uses
acme.telemetry.v1;
var
oReading: Reading;
begin
oReading := Reading.Create;
Try
oReading.LoadFromBytes(vBytes);
ShowMessage(oReading.DeviceId);
ShowMessage(FloatToStr(oReading.Value));
ShowMessage(DateTimeToStr(oReading.TakenAt.ToDateTime));
vBytes := oReading.ToBytes;
Finally
oReading.Free;
End;
end;
コマンドラインでの利用
最初に試すにはウィザードが便利ですが、proto ツリーは変化していくものであり、その再生成はビルドに組み込むべき作業です。同じジェネレーターをコマンドラインから実行できます。
sgcProtoBuf.exe --input=protos\acme\telemetry\v1\telemetry.proto --resolve-imports --output-dir=units --enum-as-type
スイッチはウィザードのオプションに対応しています。--resolve-imports、--import-root(繰り返し指定可能で、複数のルートにまたがるツリー向けです)、--output-dir、--enum-as-type、--original-names、--short-enum-consts、さらに --no-classes、--no-services、--no-docs があります。完全な一覧は --help を付けて実行してください。
入手方法
sgcProtoBuf は、TsgcGRPCClient や sgcProtoBuf ランタイムユニットとともに sgcWebSockets に同梱されています。最新のビルドは sgcWebSockets のダウンロードページから入手できます。オプションの完全なリファレンスについては、ヘルプの sgcProtoBuf トピックをご覧ください。
ご質問やフィードバック、あるいはうまく生成できない proto ツリーはありませんか。お問い合わせください。コードを書いた本人から返信が届きます。
