Tonicを用いてgRPC開発する際、サーバープロセスの構築以外に、protobufを適切に組込むことが必要になります。
Tonicの場合は、
PROST!を利用しており、protocコマンドで生成するフローとやや異なります。
なお、本記事のロジックと同等のコード生成CLIツール、 Alainをオープンソースで配布しています。
protoインクルードの構造
gRPC開発では、protobufの定義と各言語の変数との対応を意識することが重要です。
以下のようなprotobufのコードを組込む例を考えます。
syntax = "proto3";
package example.duck;
service ExampleDuck {
  rpc LameDuck(Dummy) returns (Dummy) {}
}
message Dummy {}
まず、マクロを用いてprotoをインクルードします。
pub mod example {
  pub mod duck {
        tonic::include_proto!("example.duck");
  }
}
この際、modの階層をprotobufのpackage名前空間に揃えて定義することが重要です。
構造を間違えている場合、
superがネストし過ぎるコードが出力されるといった非常に分かりにくいエラーに直面します。
proto定義の参照
インクルードしたproto定義は、mod構造に沿ってrustプロジェクトの名前空間にアサインされます。
先ほどの例では、crate::example::duck::Dummyのような名前でインポートされます。
なお、rpcはcrate::example::duck::lame_duck()のようにスネークケースになります。
この他、サーバーなどの構造体もツールの命名規約に沿って自動生成されています。target/ディレクトリに自動生成されたファイルがあり、個別のケースはコードを確認することになります。
- example::duck::LameDuckServer
 - example::duck::LameDuckClient
 - example::duck::LameDuckService
 
サーバー実装では、protoに定義したrpcをすべて実装しないとコンパイル時エラーになります。
RequestとResponseの構造
Requestは、tonic::Request<example::duck::Dummy>のようにprotobufメッセージをtonic::Requestでラップしたものです。
メッセージはinto_inner()で取り出せます。
またResponseは、Result<tonic::Response<example::duck::Dummy, tonic::Status>のように、tonic::Responseまたはtonic::Statusを返すResult型です。
ビルドスクリプト
tonicは、cargo build実行時にprotobufをrustコードにコンパイルするフローを想定しています。
そのため、以下のようなbuild.rsをプロジェクトルートに置きます。
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("proto/example_duck.proto")?;
    Ok(())
}
なお、protobufコンパイルにはtonic-buildクレートが必要であるため、Cargo.tomlのbuild-dependenciesに追加しておきます。
[build-dependencies]
tonic-build	=  { version = "0.4", features = ["prost"] }
複数のserviceを提供するServer
gRPCではserviceがrpcをまとめる単位となり、proto内に複数のserviceを定義できます。
tonicはserviceを同名のトレイトに対応させています。
各トレイトは分割実装できないためservice内のrpcの数が増えると、長大なトレイト定義をimplすることになります。
serviceを論理的に分割できる場合には、各トレイト実装を別のファイルに定義できます。
各サービスはエントリポイントのServer構築プロセスで、
add_service()を用いて追加します。
add_service()メソッドは単一のサーバーに複数指定できるため、単一ポートに複数のサービスを統合できます。