NetflixのGraphileを使った開発スタイルを理解する
- 迅速なアプリケーション開発を可能にするバックエンドプラットフォームとしてのGraphQLマイクロサービス GraphQL microservices(GQLMS)のコンセプト
- すべてのデータを一つのGraphQLスキーマに落とし込むことに価値はあるが、多大な労力を要する
- GQLMSはそうではなくて、シンプルにCRUDアプリケーションを作るためのAPI仕様としてのみGraphQLを使う
- GraphiQLは非常に高いDXを提供する(Swagger UIとは対照的に!)
- GraphQLは強力な型システムを持つ上に、優れたエコシステムでそのメンテナンスが容易(Swagger Codegenとは違って)
Graphile
Graphileはスマートコメント機能でいろいろできる。シンプルなNode.jsサーバとGraphileライブラリ(セキュリティ、ロギングなどの内部コンポーネント)を実行するDockerコンテナが、迅速な開発のための「RESTより優れたREST」または「REST++」プラットフォームを提供することができるという仮説を立て、そのアプローチが成功する中でいくつかのプラクティスを発見した。
- Database views as API
- DBのビューをAPIレイヤーとして使うことでGraphQLスキーマに影響を与えずテーブルを変更する柔軟性を得られる
- PostgreSQL composite types
- PostgreSQLの集約関数を利用する際には、PostgreSQLの複合型を利用する
- Allow “full access” to the Graphile-generated schema (during development)
- Graphileが自動生成したGraphQLクエリとミューテーションにGraphQLクライアントが「フルアクセス」できるようにして柔軟性を高め(すべてのテーブルとビューに対するCRUD操作を公開)、開発プロセスの後半でアプリが本番稼動する前にUIで使用されなかったスキーマ要素を削除する
Database views as API
実際にデータを入れるテーブルをPostgreSQLの1つのスキーマに置き、そのテーブルに対するビューを別のスキーマに定義しGraphile WebアプリはPostgreSQLの専用ユーザーロールを使ってデータベースに接続することにすることで様々なメリットが得られた。
- 基本となるテーブルはGraphQLスキーマで公開されているビューとは別に変更することができる
- ビューは基本的な変換処理を行うことができる(TIMESTAMPフィールドをISO8601文字列として表示するなど)
- 基礎となるテーブルに対するすべての権限をWebアプリケーションのPostgreSQLユーザに対して明示的にgrantすることで予期せぬ書き込みを避けることができる
- テーブルとビューの変更は単一のトランザクション内で行うことができるので公開されたGraphQLスキーマへの変更がアトミックに行われる
最後のポイントとしてはテーブルのカラムの型を変更すると、関連するビューが壊れてしまうことだが変更をトランザクションで処理することでトランザクションをコミットする前にビューを削除し、カラムを更新し、ビューを再作成することができる。pgWatchを有効にしてGraphileを実行するとDBが更新されるとすぐにGraphQLスキーマに変更が反映される
PostgreSQL composite types
PostgreSQLの集約関数やJSON関数がビュー内に存在する場合、Graphileがネストされた型を付けられない。
create view postgraphile.json_object_example as
select json_build_object(‘hello world’::text, 1, ‘2’::text, 3)
as json;
select * from postgraphile.json_object_example;
json
-- {“hello world”: 1, “2”: 3}
-- (1 row)
この場合 json
はGraphQL上では JSON
というTypeになって、フィールドの型が得られない。composite typeを定義を定義するとGraphileでもその型が定義される。コメントもGraphQLスキーマに反映され、GraphiQLから見ることができる。
CREATE TYPE postgraphile.custom_type AS (
"hello world" integer,
"2" integer
);
CREATE FUNCTION postgraphile.custom_type(
"hello world" integer,
"2" integer
)
RETURNS postgraphile.custom_type
AS 'select $1, $2'
LANGUAGE SQL;
comment on type postgraphile.custom_type is E’A description for the custom type’;
comment on view postgraphile.json_object_example2 is E’A description for the view’;
comment on column postgraphile.custom_type.”hello world” is E’A description for hello world’;
comment on column postgraphile.custom_type.field_2 is E’@name field_two\nA description for the second field’;
Allow “full access” to the Graphile-generated schema (during development)
当初、Graphile を使用するという提案は、「1 つのスキーマですべてを制御する」アーキテクチャーのオプションとして議論された際に、激しい反対意見がありました。SQLライクなクエリ・インターフェースでデータベース・テーブルにオープン・アクセスすることについて、セキュリティ(データベース内で行レベルのアクセス・コントロールを実施するためのIAMインフラとの統合はどうするのか)やパフォーマンス(一度にすべての行を選択することによるデータベースのDDoSを避けるために、クエリをどのように制限するのか)に関する正当な懸念が示されました。しかし、小規模なチームによる社内アプリの迅速な開発を目的としたGQLMSでは、Graphileのデフォルトの動作として、すべての列をフィルタリングに利用できるようにしたことで、UIチームはバックエンドチームを巻き込むことなく、多くの新機能を迅速に繰り返し開発することができました。これは、UIチームとバックエンドチームが最初のAPIコントラクトに合意し、バックエンドチームがAPIを実装し、UIチームがAPIを利用し、開発ライフサイクルの中でUIのニーズの変化に合わせてAPIコントラクトを進化させていくという他の開発モデルとは対照的です。
当初は、UIが必要なデータを取得するために複数のクエリを必要とすることが多く、アプリ全体のパフォーマンスが低下していました。しかし、アプリの動作が具体化した後は、各UIのニーズを満たす新しいビューをすぐに作成し、各インタラクションが1回の呼び出しで済むようにしました。これらのリクエストはネイティブコードでデータベース上で実行されるため、洗練されたクエリを実行し、インデックス、非正規化、クラスタリングなどを適切に使用することで高いパフォーマンスを実現することができました。
UIとバックエンドの間の "パブリックAPI "が固まったところで、GraphQLスキーマを "ハード化 "しました。テーブルやビューにスマートコメント「@omit」を付けて、(Graphileのデフォルト設定で作成された)不要なクエリをすべて削除しました。また、デフォルトではGraphileがテーブルやビューに対してミューテーションを生成していますが、スマートコメント@omit create,update,deleteを付けることで、スキーマからミューテーションを削除しています。
Conclusion
スキーマファーストのアプローチでGraphQL APIを開発する場合、Graphileの自動GraphQLスキーマ生成機能は、スキーマ設計者を許容できないほど制限することになるでしょう。細かなアクセス制御が必要な場合、Graphileを既存の企業のIAMインフラに統合するのは難しいでしょう。また、Graphileが生成したスキーマにカスタムクエリやミューテーションを追加すること(例:UIが必要とするgRPCサービスコールを公開すること)は、現在私たちがDockerイメージでサポートしていないことです。しかし、私たちは最近、GraphileのmakeExtendSchemaPluginを知りました。Graphileが生成するスキーマに、カスタムタイプ、クエリ、ミューテーションをマージすることができます。
とはいえ、限られた初期要件とアドホックな分散チーム(過去にコラボレーションの経験がない)で、4〜6週間かけて社内アプリの実装に成功したことは、Netflixスタジオ全体で大きな関心を呼びました。Netflixの他のチームも、GQLMSのアプローチに注目しています。
- 標準的なGraphQLの構造とユーティリティを使用して、データベースをAPIとして公開する。
- PostgreSQLのカスタムタイプを利用してGraphQLスキーマを作成する。
- データベースから大規模なAPIを自動生成することで柔軟性を高める
- Graphileによって生成されたものに加えて、追加のカスタムビジネスロジックとデータタイプを公開する。
また、従来はRESTを使用していた社内のCRUDツールにとっても有効なソリューションとなりました。Graphileをホストする標準化されたDockerコンテナを持つことで、チームは必要なインフラを手に入れ、この困難な時代にグローバルなメディアスタジオの刻々と変化するニーズを解決するための新しいツールのプロトタイピングと迅速なアプリケーション開発を素早く繰り返すことができます。
メモ(うろ覚え・読み違いあるかも)
- Netflixは映像制作スタジオ向けに大規模なマイクロサービスをFederationを使って統合しているが、この記事ではその「一つのGraphQLスキーマにすべてを落とし込む」アプローチとは別の、迅速な社内向けサービス立ち上げを目的としてCRUDのAPIサーバーを開発する手間を省く手段としてGraphileを使用する方法(GQLMS)を紹介している。
- FederationされたGraphQLスキーマの設計に専任を置いていたはずなので、Graphileが自動生成するスキーマを組み込むのは許容し難いけど小規模なチームで試したところ有意に開発速度でメリットがあったのでやっているという感じ?結局GraphileなサーバーがFederationに組み込まれたのか気になる。そうでないとフロントエンド的には複数のエンドポイントがある感じになりそう。多分Federationなグラフが関係ないくらいの分離された社内サービス?でもGQLMSと言ってるくらいなので全体の一部として組み込まれてる?
- この用途では道具としてHasuraとGraphileにほぼ差はない気がする。DockerとしてパッケージングするならGraphileのほうが便利だろうけどやってることは変わらなそう
- RLSとIAMで構築した権限管理を組み合わせるのが難しいとかは気付かなかった。その点でRLSを使わず、開発初期はフルオープンにしてComposite Typeとか関数を使ってスキーマを拡張していき最後はomitとかで塞ぐって感じ?
- IAMで権限管理とかやってないならRLSは便利でセキュリティ高められるので使いたそう
- たしかにこのスタイルだとサーバーサイドエンジニアはDBのスキーマ書いたらあとはほとんどやること無さそう
- DBのビューを活用する開発スタイルのDXというかCDが気になる。経験がなくわからん
- Netflixより小規模な開発でもスタイルを変えてやる価値はある?Graphile自体大規模用途だけのものではないと思うし、makeExtendSchemaPluginだとQuery/Mutationの中で他のサーバー叩くとかする余地もあるのでサービスを立ち上げる際のPostgreSQLにくっついたモノリスとして運用もできる?
- Node.jsサーバーになるのでNext.jsのAPI routeにGraphile生やすとそれでフルスタックいけるかも