Slide 1

Slide 1 text

カーナベルにおけるProtobuf 二次利用事例 2025/02/23 カーナベル社内勉強会 @andoshin11

Slide 2

Slide 2 text

今日話すこと 1. スキーマ駆動開発のおさらい + Protobuf運用の現状確認 2. ProtobufからJSON Schema validatorを生成した苦労話 3. E2E Coverage計測のためのOSSを開発した話

Slide 3

Slide 3 text

スキーマ駆動開発の おさらい

Slide 4

Slide 4 text

分散システムとスキーマ駆動開発 ● カーナベルのRepositories構成 ○ Frontend ■ selling-web, profile-web, admin-frontend ○ Backend ■ Inventory Service, Authority Service, TCG Service, etc… ○ Infrastructure ■ ka-nabell-terraform, tcg-platform-manifests ○ Schema ■ tcg-platform-proto

Slide 5

Slide 5 text

分散システムとスキーマ駆動開発 ● カーナベルのRepositories構成 ○ Frontend ■ selling-web, profile-web, admin-frontend ○ Backend ■ Inventory Service, Authority Service, TCG Service, etc… ○ Infrastructure ■ ka-nabell-terraform, tcg-platform-manifests ○ Schema ■ tcg-platform-proto API Schemaを独立したRepositoryに 切り出しているのが特徴 ↓ システム間の契約としてのスキーマの Ownershipをアプリケーションから切り離す

Slide 6

Slide 6 text

分散システムとスキーマ駆動開発 ○ 古典的なAPI運用 ■ Server > Clientという一方通行の主従関係 ■ Backend依存の同期的な開発工程 ■ 自然言語で記述された「API定義書」の保守

Slide 7

Slide 7 text

分散システムとスキーマ駆動開発 ○ モダンなスキーマ駆動開発 ■ スキーマ = システム間の契約 ■ スキーマさえ定義されていればBackend, Frontendともに非同期 な開発進行が可能に ■ 標準記法によるmachine readableなスキーマ定義 ● Protocol Buffer, Open API, GraphQL, etc… ■ 各種言語向けのServer & Clientコードを自動生成可能 ● → 仕様と実装が一致する ■ テストコードやUtilityツールも自動生成可能

Slide 8

Slide 8 text

分散システムとスキーマ駆動開発 ○ モダンなスキーマ駆動開発 ■ スキーマ = システム間の契約 ■ スキーマさえ定義されていればBackend, Frontendともに非同期 な開発進行が可能に ■ 標準記法によるmachine readableなスキーマ定義 ● Protocol Buffer, Open API, GraphQL, etc… ■ 各種言語向けのServer & Clientコードを自動生成可能 ● → 仕様と実装が一致する ■ テストコードやUtilityツールも自動生成可能 Protocol Bufferで定義された API Schemaを使い倒す

Slide 9

Slide 9 text

Protocol Bufferの二次利用 ○ API定義としてのProtobufからコード自動生成して二次利用する ○ 現在自動生成しているもの ■ APIドキュメント(protoc-gen-doc) ■ TypeScript向けの型定義(ts-proto) ■ Nest.js Server向けのMethod Handler(ts-proto) ■ gRPC-Web向けのClientコード(protoc-gen-grpc-web) ■ Request & Response型のJSON Schema(typescript-json-schema) ■ ECMASCript向けのJSON Schema Validator(ajv) ○ その他の二次利用 ■ API認可スコープの定義(Speaker Deck) ■ E2Eテストのカバレッジ計測(proto-coverage-reporter)

Slide 10

Slide 10 text

Protocol Bufferの二次利用 ○ API定義としてのProtobufからコード自動生成して二次利用する ○ 現在自動生成しているもの ■ APIドキュメント(protoc-gen-doc) ■ TypeScript向けの型定義(ts-proto) ■ Nest.js Server向けのMethod Handler(ts-proto) ■ gRPC-Web向けのClientコード(protoc-gen-grpc-web) ■ Request & Response型のJSON Schema(typescript-json-schema) ■ ECMASCript向けのJSON Schema Validator(ajv) ○ その他の二次利用 ■ API認可スコープの定義(Speaker Deck) ■ E2Eテストのカバレッジ計測(proto-coverage-reporter) 今日はこの辺の話をします

Slide 11

Slide 11 text

JSON Schema Validatorの生成

Slide 12

Slide 12 text

型安全性の静的検証と動的検証 ○ 「変数Aが〇〇型であること」をシステム横断で担保するのは難しい ■ TSの型定義では〇〇型が入っているがDBには△△型で入ってる ■ Client schemaとServer schemaのバージョン違いによる齟齬 ■ ネットワーク境界やBrowser Storageを跨ぐ際のSerialize & Desirializeによる型互換性の消失 ■ etc… ○ TS CompilerやPrisma Schemaによる静的型チェックだけでは担保に 限界がある ○ 特にBackend Serverに対して悪意のあるデータが混入するとシステム 障害やデータ汚染につながるリスクもある

Slide 13

Slide 13 text

型安全性の静的検証と動的検証 ○ やりたきこと: ■ ネットワーク境界を跨いでBackend Serverに到達するリクエス トをランタイムで厳密に検証したい ● 型チェック ● 必須プロパティのチェック ● 不要プロパティの排除 ● etc… ■ Clientに返却するデータもランタイムチェックを行ってあげたい

Slide 14

Slide 14 text

市中のソリューション ○ protovalidate ■ Protoエコシステムのリーダーであるbuf社のツール ■ Go, C++, Java, Pythonには対応しているがTS対応はなし ■ 1ヶ月ほど前にようやくTS対応スタート(issue #67) ■ 今年前半にアルファリリース予定 ○ protoc-gen-jsonschema ■ 記憶が曖昧だが、調査時点(2023年1月)で我々のプロジェクトで は利用できない問題があった ■ メンテも止まっており現在はarchive済み

Slide 15

Slide 15 text

市中のソリューション ○ protovalidate ■ Protoエコシステムのリーダーであるbuf社のツール ■ Go, C++, Java, Pythonには対応しているがTS対応はなし ■ 1ヶ月ほど前にようやくTS対応スタート(issue #67) ■ 今年前半にアルファリリース予定 ○ protoc-gen-jsonschema ■ 記憶が曖昧だが、調査時点(2023年1月)で我々のプロジェクトで は利用できない問題があった ■ メンテも止まっており現在はarchive済み 自前のJSON Schema Validatorを生成しよう

Slide 16

Slide 16 text

JSON Schema 任意のデータ型の構造定義 + 検証に利用できる標準仕様 json-schema.org

Slide 17

Slide 17 text

Protobuf → JSON Schemaの生成 ○ 直接JSON Schemaを生成できるツールはない(前述) ○ TypeScript interfaceからJSON Schemaを生成するツールはある (typescript-json-schema) ○ Protobuf → TypeScript(ts-proto) → JSON Schemaという多段変換 ならいけそう!!

Slide 18

Slide 18 text

Protobuf → TS → JSON Schema

Slide 19

Slide 19 text

typescript-json-schemaの落とし穴 ○ デフォルトのvalidation制約がイマイチ・・・ ■ string型: 空文字も許容されてしまう ■ array型: 空配列が許容されない ■ etc… ○ → そのままでは弊社のユースケースに適合しない

Slide 20

Slide 20 text

typescript-json-schemaの落とし穴 ○ annotationsを付与することで制約を変更できるらしい

Slide 21

Slide 21 text

typescript-json-schemaの落とし穴 ○ annotationsを付与することで制約を変更できるらしい ts-protoの生成したTSファイルを 事前加工する必要がある

Slide 22

Slide 22 text

TypeScript Compiler API ○ TSファイルを型レベルで解析・加工できる公式API ○ 内部的にはTSファイルを文字列ではなくAST(抽象構文木)として扱う のが特徴 ○ Reference: Using the Compiler API

Slide 23

Slide 23 text

TypeScript AST Viewerで遊んでみよう Link

Slide 24

Slide 24 text

TS interfaceにannotationを付与する ○ typescript-json-schema向けに下記のルールでannotationを付与する ○ string型 → reqreuid stringなら「@minLength 1」をcomment挿入 ○ array型 → required arrayなら「@minItems 0」をcomment挿入

Slide 25

Slide 25 text

TS interfaceにannotationを付与する ASTを再帰関数で走査(トラバース)してcommentsを付与

Slide 26

Slide 26 text

TS interfaceにannotationを付与する 加工したASTをファイル文字列に復元するprinterで出力

Slide 27

Slide 27 text

JSON Schema Validatorへの変換 ○ typescript-json-schemaでTS → JSON Schemaに変換 ○ ajvでJSON SchemaからECMAScript向けのvalidatorを生成 ■ Node.js界隈ではデファクトのツール ■ 複数のvalidation戦略 ■ JIT(Just in Time) Compile ● validation実行時にJSON schemaを読み込みにいき validation関数を内部生成する方式 ■ AOT(Ahead of Time) Compile ● 事前に各JSON Schemaに対応したvalidation関数をビルド し、関数exportする方式 ● カーナベルではこちらを利用

Slide 28

Slide 28 text

まとめ ○ 安全なBackend Server実装のために値を動的検査できるランタイム validatorが欲しい ○ エコシステムが貧弱なので自前でProto → TS → JSON Schema → JSON Schema Validatorへ変換する仕組みが必要 ○ ライブラリ制約でTS生成後にCompiler APIを用いたファイル加工が必 要 ○ ajvはAOT Compileしたstandaloneなvalidation関数を生成

Slide 29

Slide 29 text

まとめ ○ 安全なBackend Server実装のために値を動的検査できるランタイム validatorが欲しい ○ エコシステムが貧弱なので自前でProto → TS → JSON Schema → JSON Schema Validatorへ変換する仕組みが必要 ○ ライブラリ制約でTS生成後にCompiler APIを用いたファイル加工が必 要 ○ ajvはAOT Compileしたstandaloneなvalidation関数を生成 ○ → めっちゃしんどいのでprotovalidateの登場を待ち侘びています

Slide 30

Slide 30 text

E2E Coverage計測 への取り組み

Slide 31

Slide 31 text

課題: ● システムの肥大化によるAPIエンドポイントの増加 ○ Customer Service → 25 APIs ○ Inventory Service → 37 APIs ○ Selling Service → 41 APIs ○ TCG Service → 186 APIs(!?) ○ And more… ● デグレを避けるために自動テストの継続実行は必須 ● テスト品質を定量的に観測・評価できる仕組みが欲しい

Slide 32

Slide 32 text

作ったもの proto-coverage-reporter

Slide 33

Slide 33 text

作ったもの proto-coverage-reporter どのAPIのテストが不十分なのか、 全体で何%のテスト記述が完了 したのか、が追えるのが嬉しい

Slide 34

Slide 34 text

内部実装の話 ● 主に3つの機能で構成されている ○ テスト結果記録機能 ○ カバレッジ解析機能 ○ レポーティング機能

Slide 35

Slide 35 text

内部実装の話: テスト結果記録機能 ● 各テストシナリオにおいてどのAPIのどのStatusがテストされたのかを記録 する必要がある ● 各シナリオにおけるリクエスト実行の数は不定 ● gRPC Client向けのInterceptorを提供し、全てのAPI通信結果をログとし て保存する

Slide 36

Slide 36 text

内部実装の話: テスト結果記録機能

Slide 37

Slide 37 text

内部実装の話: テスト結果記録機能 ● 各テストシナリオにおいてどのAPIのどのStatusがテストされたのかを記録 する必要がある ● 各シナリオにおけるリクエスト実行の数は不定 ● gRPC Client向けのInterceptorを提供し、全てのAPI通信結果をログとし て保存する ● こだわりポイント: ○ 各OS準拠のsystem temp directoryにログを格納するようにした ○ 環境変数によるログディレクトリの指定も可能

Slide 38

Slide 38 text

内部実装の話: カバレッジ解析機能 ● カバレッジ比率計測の母数となる各Method仕様をProtobufから解析 ● Protobufには専用のextensionを用いてMethod Optionsを記述する

Slide 39

Slide 39 text

内部実装の話: カバレッジ解析機能

Slide 40

Slide 40 text

内部実装の話: カバレッジ解析機能 ● こだわりポイント: ○ extensionを定義したprotobufはPublic Repositoryにホスト ■ → 外部からもアクセス可能 ○ 現在はstatus_codesのみ対応しているがより複雑なOptionも記述可能 ○ ex: ignore rules, validation rules, etc…

Slide 41

Slide 41 text

内部実装の話: レポーティング機能 ● Jestのcustom reporter APIを利用してレポーティング機能を実装 ○ テストコンテキストの参照やLifecycle Hookを活用したテスト終了後 のログクリーンアップ等も可能 ● cli-tableとchalkを利用した見やすいレポート出力 ● こだわりポイント: ○ octokitを利用してGitHub Actionsで実行された際はPull Requestに 結果をレポーティングするようにした ○ すでにレポートコメントがある場合は都度更新を行うロジックを実装

Slide 42

Slide 42 text

Thank you!