本投稿は TECOTEC Advent Calendar 2021 の8日目の記事です。
こんにちは。決済認証システム開発事業部の鈴木です。
本投稿では表題の通り「LaravelアプリケーションからAppSync APIをIAM認証で実行する」方法についてハンズオン的に記載したいと思います。
概要
以下画像の赤矢印で示した部分が、今回の説明対象です。
フロントエンドからは、直接AppSyncで開発したGraphQL APIを実行することが可能であり、またLaravelアプリケーションからも、AppSyncを介することなく、AWS SDK for PHPや laravel-dynamodb などのライブラリを使用することでDynamoDBのデータ操作を行うことが可能であるため、一見するとEC2(サーバサイド)とAppSyncを繋ぐ矢印は必要ないように見えます。
しかし、リアルタイムデータ同期にはAppSync(GraphQL)のMutationおよびSubscriptionを利用する為、サーバサイドから直接DynamoDBにアクセスするのではなく、AppSyncを利用した方がよいこともあったりします。
目次
AppSync APIの作成
それでは早速、AppSyncの利用を開始してみましょう。
AWS サービスから AppSync を検索し、
「APIを作成」ボタンを押下します。
動作サンプルのためだけのAPIを作成するため、モデル構成等は一切考慮せず、Step1~Step3まで初期設定のまま進めていきます。
これだけで、基本的なAPIと、DynamoDBのテーブルが作成できました。
作成が完了するとクエリページに遷移します。こちらで、あらかじめデータ作成のためのサンプルクエリ(mutation)が表示されていますので、実際にAPIを実行してみましょう。
オレンジ色の▶ボタンを押してみると、クエリの右側に実行結果が表示されます。
DynamoDBのデータも見てみると、確かにデータが作成されていることが確認できました。
設定変更
初期状態では、AppSync APIを利用するための認証モードは「APIキー認証」となっています。
今回は「IAM認証」を使用したいため、この設定を変更しましょう。
AppSync > 設定 ページから「デフォルトの認証モード」にて、AWS Identify and Access Management (IAM)
を選択したら、ページ下部の「保存」ボタンを押下して変更を確定させます。
設定ページの上部で、「プライマリ認証モード」が AWS IAM
になっていることを確認し、今回使用する API URL
の値をコピーしておきましょう。
実装
さて、それではAppSync APIをリクエストするための実装を行います。
// クエリ変数として使用したい内容 $input = [ 'title' => 'Laravelからの実行テスト' ]; // GraphQLクエリ(コンソールからコピー) $query = <<<'GQL' mutation createMyModelType($input: CreateMyModelTypeInput!) { createMyModelType(input: $input) { id title } } GQL;
クエリ変数=リクエストに使用する値となりますので、コンソールのサンプルを参考に title
に値を記載します。
また、使用するGraphQLのクエリは、AWS コンソールで確認した内容をそのままコピーしてきて貼り付けます。
// リクエスト用のJSONを生成 $requestJson = json_encode([ 'query' => $query, 'variables' => [ 'input' => $input ] ]); // リクエスト情報を作成 $psr7Request = new \GuzzleHttp\Psr7\Request( 'POST', '{API URL}', // AppSync > 設定 画面でコピーした値 ['Content-Type' => 'application/json'], $requestJson );
次にリクエスト情報として、 query
にはGraphQL クエリを、 variables
には、クエリ内で指定した変数に対応する内容(今回は input
)を設定し、JSON化します。
そして、このリクエストJSONを、先ほどAppSyncの設定画面からコピーした API URL
宛てに送信するように設定します。
このままリクエストを送信しただけでは、AppSyncの認証エラーが発生してしまいますので、このリクエストに対して認証情報を付与する必要があります。
// IAM認証情報
$credentials = new \Aws\Credentials\Credentials(
'{アクセスキー ID}',
'{シークレットアクセスキー}'
);
// リクエスト情報に、V4署名を付与
$signatureV4 = new \Aws\Signature\SignatureV4('appsync', '{region}');
$signedRequest = $signatureV4->signRequest(
$psr7Request,
$credentials
);
今回はIAM認証を使用しますので、 Credentials
にアクセスキーIDとシークレットアクセスキーを設定します。
また、「対象サービス(今回は appsync
)」の「対象リージョン(東京リージョンであれば ap-northeast-1
)」に対する AWS Signature V4 署名 をつけてあげる必要がありますので、リクエスト情報と認証情報を指定して、このV4署名を付与します。
ここまでで、リクエスト必要な情報が揃いました。
後はリクエストを送信し、レスポンスを受け取るだけです。
// GlaphQL API 実行 $client = new \GuzzleHttp\Client(); $response = $client->sendAsync($signedRequest)->wait(); // response内容を連想配列に変換 $responseJson = $response->getBody()->getContents(); return json_decode($responseJson, true);
実行結果はこちら。
コンソールから実行した時と同様に、DynamoDBの方にも正しくデータが追加されていることが確認できます。
サブスクリプションの確認
冒頭で、リアルタイムデータ同期の為にAppSyncのSubscriptionを使用する、と記載しておりましたので、その動作確認も行ってみます。
先ほど作成したサンプルプログラムを、10件繰り返し実行できるような形に少し調整しました。
public function sample02() { $time_start = microtime(true); for ($i = 1; $i <= 10; $i++) { $input = [ 'title' => '複数投稿テスト' . $i, ]; …中略… // GlaphQL API 実行 $client = new \GuzzleHttp\Client(); $response = $client->sendAsync($signedRequest)->wait(); // response内容を連想配列に変換 $responseJson = $response->getBody()->getContents(); $result[] = json_decode($responseJson, true); } $time = microtime(true) - $time_start; Log::debug('{$time} 秒'); return $result; }
AppSync コンソールの方では、クエリページから、Subscriptionの onCreateMyModelType
を指定して、オレンジ色の▶ボタンを押すと右側に「1個のミューテーションにサブスクライブしました」と表示され、待機状態になります。
この状態のまま、プログラムを実行すると……。
「複数投稿テスト1~10」まで、投稿が順に検知できていることが確認できました。
この時の実行時間は 3.4753088951111 秒
です。
1回のみ実行の場合が 0.42286014556885 秒
でしたので、10倍とまではいかずとも、(直列実行なので当然ながら)それに近い時間はかかっている状態です。
並列実行
1つずつ順番にリクエストを送る必要がある場合には、上記の処理で問題ありませんが、順番を問わない処理であれば、複数のリクエストを纏めて送りたいものです。
ということで、Guzzleで非同期の並列リクエストを試してみましょう。
プログラムを以下のように書き換えます。
public function sample03(Request $request) { $time_start = microtime(true); // ループの外側でClientを生成する $client = new \GuzzleHttp\Client(); for ($i = 1; $i <= 10; $i++) { $input = [ 'title' => '並列実行テスト' . $i, ]; …中略… // GlaphQL API リクエスト ※この時点では->wait()しない $promises[] = $client->sendAsync($signedRequest); } // まとめて実行 $responses = \GuzzleHttp\Promise\Utils::all($promises)->wait(); foreach ($responses as $response) { $responseJson = $response->getBody()->getContents(); $result[] = json_decode($responseJson, true); } $time = microtime(true) - $time_start; Log::debug('{$time} 秒'); return $result; }
一つのリクエストに対して、都度同期実行(wait()
)を行っているのではなく、全てのリクエストを纏めて、 Promise\Utils::all($promises)->wait();
という形で実行している点が先ほどとの違いです。
この状態で、先ほどと同様にサブスクリプションを試してみると……。
困ったことに、目視では「並列実行テスト1」と「並列実行テスト8」しか実行されていないように見えます。
上記で指定しているサブスクリプションは、MyModelType
の全てのデータ作成を通知するものとなっていますので、これを調整して、特定の更新内容が含まれる場合にのみ通知されるような形にしてみましょう。
subscription MySubscription { onCreateMyModelType(title: "並列実行テスト1") { id title } }
title
に確認したい値を指定し、それぞれサブスクリプションを実行中の状態にした上で、先ほどと同様にプログラムを実行します。
※都合により、1つの画面には「並列実行テスト1~4」までしか並べられず、「並列実行テスト5~10」の分は、左上のウィンドウのタブを切り替えることで確認しています。
これで、各データが作成されたことが一斉に通知される状態になりました。
実行時間は 0.75495791435242 秒
となり、10回リクエストを繰り返していた時と比べると、大分実行時間が短縮できたと言えるでしょう。
おわりに
以上、AppSync APIの実行例と、サブスクリプションの確認についてご紹介いたしました。
ここまでご覧いただき、ありがとうございました。
その他参考
https://laracasts.com/discuss/channels/laravel/graphql-client-for-laravel