AWS AppSync を使う際に遭遇しやすいトラブル
こんにちは。Build サービス推進チームで Solution Architect として活動している t_maru です。
今回は AWS AppSync を使い始めた時に遭遇しやすいトラブルについて書いてみたいと思います。
AWS AppSync とは?
AWS AppSync とは、 AWS の提供しているモバイル向けのサービスで、 マネージドな GraphQL API を構築することができます。
AWS の提供しているサービスなので、 DynamoDB や Lambda に接続できることはもちろん、 HTTPS のエンドポイントも設定することができるので、例えば既存の REST API などを GraphQL で Wrap し、 Client から見た際にリソースの違い (DB なのか別の API を call しているのかなど) を隠蔽して一貫した API を提供することもできます。
AWS AppSync の詳細な説明については公式のサイトをご確認ください。
なぜ AppSync を使うのか?
Web やモバイルアプリからアクセスさせる対象としてまず思いつくのは API Gateway を使った REST API ではないでしょうか。
API Gateway を使った API を構築した場合は、データを Client が都度取得しに行く必要があり、例えばチャットアプリケーションなど、順次新しいデータがあったら画面表示したい場合などに一工夫が必要になってきます。
Amazon API Gateway について厳密に言えば WebSocket に対応しているので、やろうと思えばリアルタイムにデータを Client に届けることもできますが、開発する機能が増えてしまうためあまりおすすめできる方法では無いと思います。
そこで候補として出てくるものが AppSync (GraphQL) だと思います。 GraphQL にはサーバ側で起こったイベントを Client 側でリアルタイムに検出できる `Subscription` という仕組みがあり、これを使うことで先程の例だと、チャットルームに新しいメッセージがあったら動的にデータを取得して画面表示するという部分が比較的簡単に実装できるようになります。
AppSync にはリアルタイムにデータを取得するだけでなく、オフライン機能も組み込まれているためネットワークが不安定な環境で使うアプリケーションなどを開発する際にも力を発揮すると思います。
今回は GraphQL の特徴のうち、リアルタイムにデータを取得できるという部分にフォーカスして執筆していますが、 GraphQL を使うと REST API を使った場合と比較して Client が API を call する回数を減らせる、オーバーフェッチ、アンダーフェッチを減らせるといったメリットもありますので、興味のある方は調べてみてください。
因みに `リアルタイムにデータを取得する` という点に注目すると、 Google Cloud の Firebase というアプリ開発向けのサービス郡の中に含まれている Cloud Firestore、 Realtime Database というサービスもあります。こちらもリアルタイムにデータの作成/更新などのイベントを Client 側に通知する機能が存在しており、チャットアプリケーションなどのようにリアルタイムにデータ変更を活用したい場合にとても有効です。
AppSync を使う際に最初に遭遇しやすいトラブル 2 つ
1 つ目の問題は、 Management Console で Subscription の動作確認をしようとすると遭遇する可能性があります。
AppSync の Console から `Queries` のメニューを開くと Mutation のみサンプルとして記載されているので、 Subscription の項目を追記して実行ボタンを押すと、以下のようなエラー画面になると思います。
エラーメッセージ
"message": "Connection failed: {\"errors\":[{\"errorType\":\"UnsupportedOperation\",\"message\":\" not supported through the realtime channel\"}]}"
Subscription の書き方が間違ってるのか?と疑ったのですが、どうやら Subscription を AppSync の Console 上で実行するには、 Subscription 以外の Query を書いてはダメというだけの話でした。
2 つ目も Subscription に関連するトラブルで、 Client 側では特にエラーも出ず Subscription の開始はできているのですが、指定しているイベントが検出されない問題です。
Client で使っている GraphQL の定義はこのようになっています。 (以下、TypeScript で書いています。)
export const createMessage = `mutation CreateMessage(
$conversationId: ID!
$createdAt: String!
$content: String!
$id: ID!
) {
createMessage(conversationId: $conversationId createdAt: $createdAt content: $content id: $id) {
content
createdAt
}
}`;
export const subscribeToNewMessage = `subscription SubscribeToNewMessage(
$conversationId
) {
subscribeToNewMessage(conversationId: $conversationId) {
conversationId
content
createdAt
}
}`;
Amplify SDK 経由で GraphQL のエンドポイントに接続すると、特にエラー等も発生せず subscribe 自体は実行されているようなのですが、 `createMessage` を実行しても追加されたメッセージのイベントは流れてきません。
色々と調査した結果、 `createMessage の mutation の戻り値に conversationId が含まれてなかった` ことが原因のようで、以下のように createMessage を変更したところ、 `createMessage` 実行直後に追加されたメッセージのイベントを Client で受け取ることができました。
export const createMessage = `mutation CreateMessage(
$conversationId: ID!
$createdAt: String!
$content: String!
$id: ID!
) {
createMessage(conversationId: $conversationId createdAt: $createdAt content: $content id: $id) {
conversationId "<-- これを追記しただけ"
content
createdAt
}
}`;
公開されている Amplify や AppSync、 Apollo 関連のソースコードを探ってはみましたが、この挙動を引き起こしている実装は見つけられなかったので対処療法的な形になってしまいますが、 subscription を使う際には条件で使用する field は mutation の戻り値にも含まれている必要があるようです。
まとめ
今回紹介した AppSync に関連するトラブルは初歩的なものなので、サンプルコード以上の開発をしようと思うともっと違った問題に遭遇するかと思いますが、AppSync (GraphQL) は REST API の思想とは異なり、 Client 側で扱いやすいようにレスポンスされるデータの field を指定できる点 (何度もリクエストを投げて Client 側で必要な状態を作る必要がない点) やリアルタイムにデータの作成/更新のイベントを受け取れる点が便利ですので、もし既存システムの更新や新たにサービスを作る際には検討のテーブルに挙げてみても良いのではないかと思います。
※ REST API をすべて GraphQL に置き換えることが必ずしも良い結果をもたらすとは限りませんので、 API を使って実現しようとしていることに対して使おうとしている技術が適切なのかはしっかり検討してください。