はじめに
私が普段業務で扱っているアプリケーションでは、フロントエンドとバックエンドの通信にGraphQLを採用しています。
これまでなんとなくでGraphQLを使っていたところだったのですが、実のところ基本部分から理解できていないと感じていたため、初めてのGraphQLという書籍で改めて基礎から学習しています。
書籍を読み進めていく中で特に理解に時間がかかった箇所があったので、その内容について自分の理解も兼ねて記事に残してみようと思います。
感じた疑問
疑問に感じたのは以下のようなmutationの例です。
※書籍でいうと3.3の項に出てきたものをそのまま使っています。
mutation CreateSong {
addSong(title: "No Scrubs", numberOne: true, performerName: "TLC") {
id
title
numberOne
}
}
シンプルに曲を追加するためのmutationです。
一見何の変哲もないように見えますし、私も最初は特に疑問を抱くことはありませんでした。
しかしじっと見ていると、素朴な疑問が浮かんできました。
「CreateSongとaddSong、どうして似たようなワードを書かなくてはいけないのか?」
という疑問です。
疑問の答え
CreateSong は operation name、つまりこの mutation operation に付けた名前です。
一方、addSong は GraphQL のスキーマ、より具体的には Mutation 型に定義される mutation field の1つです。
mutation CreateSong {
addSong(title: "No Scrubs", numberOne: true, performerName: "TLC") {
id
title
numberOne
}
}
この例では、mutation が operation type、CreateSong が operation name、addSong が実際に実行される mutation field です。
つまり、CreateSong と addSong は似たような名前に見えますが、役割が違います。
-
CreateSong- クライアント側で定義する operation の名前
- ログ、デバッグ、複数 operation の識別などに使われる
-
addSong- サーバー側のGraphQLスキーマに定義された mutation field
- 実際に曲を追加する処理に対応する入口
両者は似ているようで、異なるレイヤーの定義です。
そのため、それぞれ別に存在しています。
わかっている人からしたら「そりゃそうでしょ」となるような帰結だと思います。
もう少し詳しく
上記で出した例は簡単すぎて、正直2つの違いはわかりにくいんじゃないかと思います。
operation name が真価を発揮するのは、例えば下記のように異なる文脈で addSong を呼びたい場面です。
mutation CreateSongFromAdminForm($input: AddSongInput!) {
addSong(input: $input) {
id
title
}
}
mutation ImportSongFromCsv($input: AddSongInput!) {
addSong(input: $input) {
id
title
performer {
id
name
}
}
}
2つのmutationがあり、下記のような違いが見て取れます。
-
名称が違う
- 1つ目は管理画面から呼ばれそう
- 2つ目はCSVのインポートから曲を作成するときに呼ばれそう
-
要求する selection set が異なる
- 1つ目は
id,titleのみ - 2つ目は
performerも要求する
- 1つ目は
このように、同じ addSong という mutation field を実行する場合でも、呼び出されるコンテキストが異なったり、要求する selection set が異なったりする場合には、クライアント側では別々の operation として定義できます。
1つの GraphQL Document に複数の operation が含まれる場合、実行時にはどの operation を実行するかを指定する必要があります。
また、operation name が付いていることで、ログやデバッグ時にも「どの用途のリクエストなのか」を追いやすくなります。
たとえば同じ addSong を呼び出していても、operation name を見ることで、管理画面のフォームからの作成なのか、CSVインポートからの作成なのかを区別しやすくなります。
考えてみれば当たり前
ここからは余談です。
私は上記のような具体例が出てきて初めて納得できたのですが、GraphQLの根本の特徴・メリットに照らしてみれば当たり前のことだったように思います。
いろんな特徴はあるかと思いますが、今回の話題で重要なのはGraphQLが下記のような流れで機能するところです。
- スキーマファーストでデータ・データ取得(Query)・更新(Mutation)の型を定義する
- GraphQLサーバーは定義されたスキーマに従ってデータを返却する処理(Resolver)を実装する
- クライアントは用意されたスキーマを自由に組み合わせてデータ取得・更新を行う
上記のような手順を踏むことで、クライアントは柔軟なクエリ・ミューテーションの組み立てを行うことができ、オーバーフェッチを防いだり、バックエンドの新規API作成を待たずとも開発を進めたりできます。
つまり、GraphQL のメリットを活かすように使っていれば、1つの mutation field に対して複数の operation が存在することは自然に起こり得ます。
operation name と mutation field が1対1に対応して見えるのは、あくまで簡単な例だからです。
これに気づけなかったのは、私が普段開発に携わっているアプリケーションでは、基本的に operation name と mutation field が1対1で対応していることが多かったからです。
※正直これはGraphQLの特徴を活かせているとはいえない作りではあるのですが、それはそれとして目的を持った設計なので、ここで是非を論じる意図はありません。
感想
私が普段関わっているアプリケーションには既に1年半くらい触れているので、わかった気になっていた部分がありました。
しかし、いざ基本を学んでみると、やはり本当の意味で使用技術を理解できていなかったのだと痛感しました。
今後も目の前の実装を当たり前と思わず、なぜそうなっているのか、もっと良くするにはどうしたら良いのかを自分なりに整理することが大事だなと思います。