見出し画像

GraphQL APIで複数の命令を1つのリクエストで投げる方法

最近初めてRESTではなくGraphQLという規格で作られたAPIを触ったのですが、その際に詰まった複数のmutationを1つのリクエストで投げる方法についてシェアします。

GraphQL APIでは複数の命令を一つのリクエストにまとめられる

GraphQL APIとは?
GraphQLとはクエリ言語(Query Language)であり、SQLのようにクエリを使って情報の取得や更新を行うための仕組みです。GraphQL APIは、HTTPベースでGraphQLに則ったクエリを用いてクライアントからサーバ側データの取得・更新などをできるようにしたものです。ですので、初めは「RESTに代わる新たなWebAPI規格」と理解するとイメージしやすいかと思います。

GraphQL APIの特長
REST APIでは「ユーザ情報取得」や「ユーザ作成」などの機能ごとにエンドポイントが用意されているのが一般的です。一方GraphQL APIでは全ての機能で共通のエンドポイントに対し、利用する機能(命令)をクエリで指定します。

DBにSQLを投げるとき、どのテーブルにどんな操作をするかによって接続先は変わりませんよね。DBの接続先は共通で、その中でどのテーブルにどんな操作をするか?をSELECTやUPDATEなどのクエリで指定します。それと同じノリです。

この共通のエンドポイントにクエリを送信するという特性上、GraphQL APIでは、複数の命令を一つのリクエストにまとめることができます。

なお、API利用者目線でのGraphQLとRESTの違いについてはこちらでより詳細に書きました。

今回用いる例

今回私が触ったのは、CI/CDツールのHarnessのAPIです。具体例として、この中のユーザを作成するAPI "Create a User"を用います。GraphQLの命令にはいくつか種類があり、情報の更新を行う命令は mutation と呼ばれます。これはRESTでいうPOSTやUPDATEに対応するものです。この命令は同時に1件のユーザしか作成できませんが、一つのリクエストに複数の命令を書くことで、複数のユーザを同時に作成してみます。

mutationの投げ方

まずはドキュメントにあるサンプルをGitのガイドを参考に1件のmutationの投げ方を読み解いてみます。

クエリ
----------
mutation createUser($user: CreateUserInput!) {
 createUser(input: $user) {
   user {
     id
     email
     name
     userGroups(limit: 5) {
       nodes {
         id
         name
       }
     }
   }
   clientMutationId
 }
}

変数
----------
{
 "user": {
   "name": "Joyce Silva",
   "email": "jsilva@example.com",
   "clientMutationId": "ssdsdsecved",
   "userGroupIds": ["7mejLPDtRvOCF8K3roIEVg"]
 }
}

クエリ部分に命令が書かれており、クエリに埋め込まれた変数 $user の具体的な値が変数部分で与えられています。ここからは部分ごとに区切ってみていきます。

クエリ
----------
mutation createUser($user: CreateUserInput!) { ...中略... }

変数
----------
{
 "user": {
   "name": "Joyce Silva",
   "email": "jsilva@example.com",
   "clientMutationId": "ssdsdsecved",
   "userGroupIds": ["7mejLPDtRvOCF8K3roIEVg"]
 }
}

初めの mutation は命令の種別です。データの作成や更新を行うことを意味します。次の createUser ですが、実はこの文字列に意味はありません。クエリに対して任意で名前を付けることができます。続く括弧ではこの命令において使う引数を定義しています。CreateUserInput型のuserという名前の変数を定義する、という意味です。ですので、"CreateUserInput"はサーバ側で定義されているデータ型です。"user"は変数名ですので自由な名前を付けることができます。最後の"!"は必須変数であることを意味しています。

変数部分には、クエリ側で定義された変数の具体的な値をJSONで指定します。ここで、変数の中に"user"が存在していなかったり、渡した値がサーバ側で定義されている型とは異なる場合、エラーが返ってきます。

クエリ
----------
mutation createUser($user: CreateUserInput!) {
 createUser(input: $user) {
   user {
     id
     email
     name
     userGroups(limit: 5) {
       nodes {
         id
         name
       }
     }
   }
   clientMutationId
 }
}

次に命令の中身を見ていきましょう。createUser(input: $user)は、サーバ側で定義されているcreateUserというmutationを変数"user"を引数にして呼び出す、という意味です。mutationでは常に"input"が必須の引数になるようです。

そして、そのあとの中括弧 { } の中身はレスポンスとして返してほしいデータの構造を指定します。RESTでは一般的にレスポンスの構造はAPI仕様として固定されますが、GraphQL APIではレスポンスのデータ構造をクエリによって指定することができます。ID、メールアドレス、名前、所属グループを返すようにしていることがわかります。

clientMutationIdですが、こちらはリクエスト(クエリ+変数)側で指定した値がそのままレスポンスで返ってきます。これを使って特定の命令を追跡するためのIDを振ることができます。GraphQLのガイドのサンプルでもついていたので、mutationでは共通でclientMutationIdが利用できるものと思われます。

複数のmutationを1つのリクエストで投げる

先ほどの命令を例に、複数のmutationを1つのリクエストで投げる例を示します。

クエリ
----------
mutation createUsers($user1: CreateUserInput!, $user2: CreateUserInput!) {
 createUser1: createUser(input: $user1) {
   user {
     id
     email
   }
   clientMutationId
 }
 createUser2: createUser(input: $user2) {
   user {
     id
     email
   }
   clientMutationId
 }
}

変数
----------
{
 "user1": {
   "name": "Joyce Silva",
   "email": "jsilva@example.com",
   "clientMutationId": "ssdsdsecved",
   "userGroupIds": ["7mejLPDtRvOCF8K3roIEVg"]
 },
 "user2": {
   "name": "Joyce Silva",
   "email": "jsilva@example.com",
   "clientMutationId": "ssdsdsecved",
   "userGroupIds": ["7mejLPDtRvOCF8K3roIEVg"]
 }
}

ポイントはcreateUser()の前に"createUser1: "として名前を付けている部分です。こちらの記事などのサンプルを見ると、異なる命令を一つのリクエストに含む場合はそのまま並べればよいのですが、同じ命令を一つのリクエストに複数含む場合は、そのまま並べるとエラーが出るようです。こちらを参考にして、上記の例のように命令の前に異なる名前を付けてあげる必要があることがわかりました。

補足 そもそもエンドポイントと認証情報の投げ方でハマった

複数のmutationを1つのリクエストで投げる方法については以上ですが、今回のAPI調査において、そもそもリクエストを投げるエンドポイント、投げ方(認証情報などの設定の仕方)にはまったので、そちらも補足しておきます。

まず、APIリファレンスを見ると、
・オンプレ版の場合のエンドポイントは "https://harness.<your-domain>/gateway/api/graphql?accountId=<your-harness-account-id>\"
・認証はAPIキーをリクエストボディに"x-api-key"という名前のパラメータとして渡す
と書いてあるのですが、環境構築時の設定に依存するのか、実際には全然違う投げ方でないとダメでした。

では、どのようにしてその全然違うやり方がわかったかというと、HarnessにはHarness API Explorerというものがあり、Harnessの管理画面から自身の環境に対してクエリと変数をブラウザ上で指定してAPIコールを行う機能があります。この機能によって投げられるリクエストをChromeのDeveloper toolのNetworkタブで監視してリクエストを確認したところ、正しいエンドポイントと認証キーの制定方法がわかりました。

最近は別のシステムでもAPI Explorerが実装されているのを見たことがあるので、おそらく今後APIとセットでAPI Explorerが実装するケースが増えていくと思います。その場合、具体的なアクセス方法を手っ取り早く知るためのテクニックとして勉強になりました。

参考



この記事が気に入ったらサポートをしてみませんか?