Nav Inc.は、GraphQL構文を使ってイベントとメッセージ形式を定義するオープンソースのスキーマ定義とコードジェネレーターを作成した。GraphQLは、開発者の間で表現力と親しみやすさから選ばれたが、それは構文にのみ使われる。Nav Schema Architecture(NSA)ではGraphQLランタイムは使われない。
GraphQLを使うと、コントラクト開発者はデータモデルとメッセージ形式の両方を同時に記述することができる。2つのセマンティクスのセットは必要ない。これは、ベースになるデータモデルで属性がオプションである場合に便利だが、そのモデルが特定のメッセージで使用される場合は必須となる。
NSAの主な目的は、GraphQLを使ったルート定義に基づいて、複数の言語でコードとスキーマを生成することである。アウトプットは、protobufやJSON Schemaなどの他のスキーマ言語、あるいは現在サポートされているGo、Ruby、Pythonのコードにすることができる。
共通のデータモデルの利点は、その実装を複数のチームやサービスに横断で簡単に広めることができることである。ビルドパイプラインでは、featureブランチでのスキーマの変更が監視され、すべてのターゲット言語の出力を生成するためにセカンダリパイプラインが起動される。次に、そのアウトプットはfeatureブランチにコミットされ、開発者はmainブランチにマージする前に変更をレビューできる。関連するすべての言語固有の出力パッケージがリビルドされ、バージョン管理され、タグが付けされる。
InfoQは、プロジェクトのNavで開発者の何人かと会った。その目的は、彼らが解決しようとしている問題と、このアプローチから得た利点をより適切に理解するためである。
InfoQ: コントラクトファースト開発は新しいアイデアではありませんが、OpenAPIとJSONスキーマがコントラクトの定義に使用されることをよく見ます。コントラクトに関する最も信頼できる情報源(source of truth)としてGraphQL構文を使い、それからコントラクトを導き出すことにした理由は何ですか。
Nav開発チーム: GraphQLを使うことにした理由はいくつかあります。GraphQLと、OpenAPIやJSONSchemaなどの他のシステムとの違いは、GraphQLには共通のデータモデルとメッセージスキーマの両方を定義する手段が含まれていることです。これは、同じ問題の2つの側面です。効果的なシステムでは、両方を簡単に定義できる必要があります。GraphQLはペイロード記述言語です。これにより、1つのドメイン固有言語で検証ルールとメッセージスキーマの両方を含むペイロードを定義するという問題が解決されます。この言語には、他のインターフェース定義言語と同じように、GraphQLベースの型システムが含まれています。この型システムは、スカラー、オブジェクト、列挙、そして、これらの型の値の基本的な検証などの要素をサポートしています。この型システムを使って、ペイロードとカスタム検証ルール(データ形式、許容値の範囲、正規表現のマッチング、必要な属性など)を定義します。メッセージコントラクトは、ペイロードタイプに基づく単なるメッセージスキーマ定義です。メッセージコントラクトを定義するとき、ペイロードタイプからメッセージコントラクトに含めるフィールドを選択できます。
もう1つの理由は、GraphQL構文は人間が読める形式であり、JSONスキーマと比較して操作がはるかに簡単であるということです。これにより、チーム間のコミュニケーションが円滑になります。
NSAを使って、単一のGraphQL Common Information Modelから、言語固有のメッセージ構造と、JSONおよびProtobufスキーマをすべて生成します。そのため、NSAは、コード生成に加えて、GraphQLをJSON/Protobufスキーマに変換するために使われています。
InfoQ: システムアーキテクチャは主に非同期メッセージングを使っていますか。それともリクエスト/レスポンスですか。NSAはどちらのアプローチにも適用できますか。
Nav開発チーム: 現在、私たちのシステムアーキテクチャはNSAプロジェクトを非同期で活用しています。イベントをAWS Eventbridgeにパブリッシュし、AWS Simple Queue Serviceからコンシュームしています。同じNSA出力コードを使って、Eventbridgeメッセージにシリアル化する前にプロデューサーでメッセージを検証することができます。また、コンシューマーのSQSからのデシリアライズされたメッセージを検証できます。
ただし、NSAはリクエスト/レスポンスシステム内でも同じように簡単に使うことができます。AWS EventbridgeとSQSと同様に、NSA出力構造はJSONあるいは他の構造化データ形式との間でシリアル化できます。実際、NSAの出力ターゲットの1つは、Googleのプロトコルバッファです。
NSAでは、検証に重点が置かれています。これはエンドポイント管理から切り離されています。NSAには、エンドポイント、サブスクライバー、パブリッシャーへの参照はありません。NSAからのアウトプットコードはアダプターで使用できます。アダプターはそれ自体が送信方法を管理します。
InfoQ: 他にどのようなデザインを検討しましたか。これが最善のアプローチであるとどのように判断しましたか。具体的には、コード生成の構文としてOpenAPI/AsyncAPIあるいはprotobufを使うことを検討しましたか。
Nav開発チーム: 現在のアーキテクチャでは、AsyncAPIなどの冗長な非同期ツールを利用する必要はありません。
AsyncAPIでは任意のメッセージペイロードを持たせることができます。そのため、NSAで生成された出力をAsyncAPIメッセージスキーマとして使用できます。私たちはNSAのアウトプットターゲットとしてProtobufメッセージ定義を間接的に使っています。
AsyncAPIでは、AWS EventBridgeと組み合わせて不要なトランスポートに対処しようとしています。さらに、検証と送信ロジックを組み合わせると、システムが複雑になる可能性があり、その課題を分離して扱うことで開発が容易になるようにしています。
InfoQ: GraphQLスキーマは別々のリポジトリに保存されていますか。それともプロデューサーやコンシューマーのいずれかと共に保存されていますか。
Nav開発チーム: GraphQLスキーマは現在、プロセッサーと、その後に生成されるコードが同じリポジトリーに保持されています。生成されたコードはメッセージの検証にのみ関係するため、Nav内の多くのライブラリやアプリケーション(プロデューサー、コンシューマー、あるいは、単純なドキュメントツール)で依存するものとして使われています。
私たちのプロジェクトはMonorepoとして存在していますが、同じようにする必要はありません。プロジェクトを責任によって複数のリポジトリに分割することもできます。1つ以上のリポジトリにGraphQLとその型拡張を含めることができます。それらは最終的にパーサー入力として1つのスキーマにマージされます。他にも、リポジトリに、パーサー自体を格納することもできます。そのパーサーは、サブモジュールとして1つまたは複数のコード生成リポジトリに接続することができます。リポジトリの第4層には、生成されたコード(言語ごとに1つのリポジトリ)と、必要なすべての検証、テスト、パッケージ化ロジックを含めることができます。最後に、送信メカニズムに関するロジックを含まないこれらのパッケージは、クライアントライブラリによって使われる場合があります。
このディスカッションに参加したNavの開発者は、Daniel Zemichael氏、Michal Scienski氏、Jovon McCloud氏、Jeff Warner氏である。
編集者注:この記事の以前のバージョンには、質問に対する部分的な回答が含まれていた。回答を2022-05-07に更新している。InfoQはこの誤りをお詫びする。