こちらは Perl Advent Calendar 2018 の 25 日目です。
gRPC を Perl でやってみようと思ったのですがインターネット上に Perl で Protobuf を扱うような文献がなかったので、まずはここから書かないとだめでしょう!ということで内容を変更してお届けします。
Perl で Protobuf を扱うのに適したモジュールは 2018 年現在だと Google::ProtocolBuffers::Dynamic が一番良さげっぽい。しかし、proto2 には対応しているが proto3 の機能に一部対応できてないとのこと*1。
Synopsis を試す
Synopsis に記述されていたコードを proto3 で試してみる。
use feature qw/say/; use Data::Dumper; use Google::ProtocolBuffers::Dynamic; my $dynamic = Google::ProtocolBuffers::Dynamic->new; $dynamic->load_string("person.proto", <<'...'); syntax = "proto3"; package humans; message Person { string name = 1; int32 id = 2; } ... $dynamic->map({ package => 'humans', prefix => 'Humans' }); # encoding/decoding my $person = Humans::Person->decode_json('{"id":31,"name":"John Doe"}'); say Dumper $person; # field accessors $person->set_id(77); say $person->get_id;
実行結果は下記の通りになった。
$VAR1 = bless( { 'name' => 'John Doe', 'id' => 31 }, 'Humans::Person' ); 77
動きとして
load_string
メソッドの第一引数に proto のファイル名を記述して、第二引数に protobuf のコードを渡すmap
メソッドを活用すると protobuf の内容に沿った Perl のオブジェクトが作成されるdecode_json
へ渡された json をベースに値をセットする
また、アクセサも提供されているため値を変更することが可能である。
map メソッドのドキュメントを読んでみると to => "perl_package"
とできるし、マッピングのオプションとして options => { ... }
ということも可能らしい。
使えるシンタックス
すべて可能なわけではないらしいので、現実的によく使われるであろう構文を読み込ませてみる。
repeated
message Person { repeated int32 id = 1; }
実行結果
$VAR1 = bless( { 'id' => [ 31, 10 ] }, 'Humans::Person' );
message
message Person { Info info = 1; } message Info { int32 age = 1; }
実行結果
$VAR1 = bless( { 'info' => bless( { 'age' => 10 }, 'Humans::Info' ) }, 'Humans::Person' );
個人的にこの結果はとても好きです。簡単にオブジェクトを作れるのが凄い。
oneof
message Person { oneof sex { bool man = 1; bool woman = 2; } }
{"man":true}
といった json を渡してあげた結果がこちら
$VAR1 = bless( { 'man' => 1 }, 'Humans::Person' );
{"man":true,"woman":false}
の文字列を渡した場合
$VAR1 = bless( { 'woman' => '' }, 'Humans::Person' );
と一つのフィールドだけ出現することが確認できた。
enum
message Person { enum Type { MAN = 0; WOMAN = 1; NO_GENDER = 2; } Type type = 1; }
{"type":2}
の文字列を渡した場合
$VAR1 = bless( { 'type' => 2 }, 'Humans::Person' );
が出力された。しかし、type に 2 よりも大きい数値を渡すとどうなるのだろうか...
{"type":3}
の文字列を渡してみる。結果は以下の通りになった。
$VAR1 = bless( {}, 'Humans::Person' );
ENUMERATIONS を読んでみると、デフォルトで check_enum_values
というオプションは有効になっている。有効になっていると、デコード時にもし該当しない enum の数値が渡されるとそれは無視する。エンコードする時はエラーを出力するとのこと。
ちなみに
my %tbl = %Humans::Person::Type::; say Dumper \%tbl;
というコードを追記して何が定義されているかを確認すると
$VAR1 = { 'WOMAN' => *Humans::Person::Type::WOMAN, 'enum_descriptor' => *Humans::Person::Type::enum_descriptor, 'MAN' => *Humans::Person::Type::MAN, 'NO_GENDER' => *Humans::Person::Type::NO_GENDER };
という結果が得られた。これはドキュメントに記載されてる通り、下記は同じ関係であることが読み取れる。
enum Foo { BAR = 1; BAZ = 2; }
use constant { BAR => 1, BAZ => 2, };
services
service Echo { rpc Say(SayRequest) returns (SayResponse); }
のような構文も解析可能らしい。が、色々準備が必要な感じだったため今回はパス。
感想
正直ここまでちゃんとパースして Perl のコードへ落とし込めるとは思ってなかった。想像以上の出来だったのでとても嬉しい。
しかし services に関して、ちゃんと動かすために Grpc::XS が必要っぽい。
これを実行するための環境を用意するのが少しハードル高い気がする。今日はここまで!
それにしても YAPC::Tokyo 楽しみですなー