この記事は弁護士ドットコム Advent Calendar 2024の 9 日目の記事です。
クラウドサインのエンジニアの榎戸です。今年は新規事業をやったりで刺激的な 1 年でした。
そんな新規事業ですが、頭を使わないお絵描きをしてみんなでアイスブレイクする時間が癒しでした。この記事のアイキャッチはそのお絵描きから生まれたものです。「protovalidate の記事書くからお絵描きイラスト募集してます」と声をかけたらアイスブレイクで描いていただきました。左上の山を登る人から、バリデーションチェックの大変さを感じさせられますね(?)
今回は、その新規事業で採用した protovalidate の体験が非常によかったので、実際に利用したスキーマの記述例をご紹介します。
protovalidate について
protovalidate は、 Protocol Buffers のスキーマに記述を追加することでスキーマの検証を可能にするもので、 protoc-gen-validate の後継となるものです。スキーマは以下のようにして簡単に記述できます。
message Request { string id = 1 [(buf.validate.field).string.uuid = true]; }
スキーマ自体にバリデーションルールが記述されることで、事前条件を認知しやすくなります。protobuf で記述されるスキーマ自体が JSON や YAML の形式で記述されるスキーマより読みやすいことも相まって、開発体験の向上が望めます。
バリデーションを行う際も、以下のような記述のみで簡単に実装できます。
func (s *FooService) Get(ctx context.Context, req *foo.GetRequest) (*foo.GetResponse, error) { if err := protovalidate.New().Validate(req); err != nil { return nil, err } // 略 }
これによって、開発者の関心ごとはいかにスキーマに事前条件を書き込むかに絞られるようになり、記述も簡潔でビジネスロジックの見通しがよくなるので、コード品質の向上が望めます。
protovalidate の実装例
ここからは、実際に新規事業で利用したスキーマの記述例を紹介します。
GraphQL DataLoader 利用を意識したリストの検証
新規事業では GraphQL を採用しており、関連して DataLoader を利用する場面が多く存在します。DataLoader は key 列をもとにリソースを取得するので、key 列としてリソースの id の文字列配列をリクエストとして受け取って、それぞれに対応するリソースを返却していました。
この時、返却するリソースの要素数が key 列の要素数と異なる場合は、 DataLoader 側でエラーになります。事前条件として要素の重複がないことを検証することで、リクエスト側の問題を検知しやすくしていました。
そこで、以下のような記述を採用しています。
message Request { repeated string ids = 1 [ (buf.validate.field).repeated.min_items = 1, (buf.validate.field).repeated.unique = true, (buf.validate.field).repeated.items = { string: {uuid: true} } ]; }
実装したルールを上から列挙すると、以下のようになります。
- リストの要素数が 1 以上
- リスト内の要素は重複しない
- リスト内の要素はすべて UUID のフォーマットである
このようにルールが複雑になっても、記述は簡潔で読みやすいことがわかります。
他のフィールドの要素を検証に利用する
新規事業では、関連するリソースをグラフのように扱うことがあり、リンクを作るビジネスロジックが存在します。リンクを作るロジックを実装するにあたって、リンクが循環しないようにする必要がありました。
ある点 from
から任意の複数の点 to
を繋ぐような場合、循環しないことのバリデーションチェックは、 from
が to
に含まれないことを検証するだけです。しかし、 UUID の検証など別のバリデーションルールもあるため複雑になります。
そこで、以下のような記述を採用しています。
message CreateLinksRequest { string from = 1 [(buf.validate.field).string.uuid = true]; repeated string to = 2 [ (buf.validate.field).repeated.min_items = 1, (buf.validate.field).repeated.max_items = 10, (buf.validate.field).repeated.unique = true, (buf.validate.field).repeated.items = { string: {uuid: true} } ]; option (buf.validate.message).cel = { id: "CreateLinks.from_to" message: "from is not in to" expression: "this.to.all(t, this.from != t)" }; }
実装したルールを上から列挙すると、以下のようになります。
- リンク元
from
は UUID のフォーマットである - リンク先
to
の要素数は 1 以上 10 以下 - リンク先
to
の要素は重複しない - リンク先
to
はすべて UUID のフォーマットである - リンク元
from
がリンク先to
の要素でないこと
このように、ルールがより複雑になっても、記述は簡潔で読みやすいままを維持できています。
まとめ
この記事では、protovalidate を使用したバリデーションルールの記述例について紹介しました。Protocol Buffer 自体が JSON や YAML よりも読みやすいため、開発体験の向上にも寄与します。
皆さんもぜひ protovalidate を試してみて、その便利さを実感してみてください。