3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AWS AppSync】Amplify Gen 2 の裏側で動く「スマートな中継機能」を理解する

3
Posted at

はじめに

                                            
AppSync.png
AWS AppSync

Amplify Gen 2 でアプリ開発していると良く使われる AWS AppSync ですが、これまで深く考えずにKiroちゃんの勧められるままに使ってきました。

そんな脇役的なイメージの AppSync ですが、ちょっと気になってきたので調べてみました。

本記事では、AppSync が Amplify Gen 2 の中でどんな役割を果たしているのか、「中継機能」という切り口で整理してみます。

まずは結論から

  • AppSync は フロントエンドとデータソースの間に立つ「スマートな中継機能」
  • ただリクエストを転送するだけでなく、ルーティング・認証認可・データ変換 を担う
  • Amplify Gen 2 は、AppSync を TypeScript だけで構築する自動化レイヤーを有する

想定される読者

  • Amplify Gen 2 を触り始めた方(「data って何が動いてるの?」と思った方)
  • AppSync の名前は知っているけど、役割がぼんやりしている方
  • API Gateway との違いを理解したい方

そもそも GraphQL って何?

AppSync は GraphQL を用いて動作するフルマネージドなAPIです。
このため、AppSync の話に入る前に、GraphQL について整理しておきます。

REST API の「あるある」な悩み

代表的なAPIといえば REST API ですが、REST API は「エンドポイントごとに返すデータが固定」なので、使いづらい場面もあるかと思います。

例えば、以下のような。

  • 欲しいデータが足りない(Under-fetching):ユーザー情報と注文一覧が欲しいのに、/users/123/users/123/orders のように2回リクエストが必要
  • 余計なデータが多い(Over-fetching):名前だけ欲しいのに、住所やメールなど全部返ってくる

GraphQL のアプローチ

GraphQL は、この問題を「クライアントが欲しいデータを自分で指定する」というアプローチで解決します。

# REST だと2回のリクエストが必要だったものが、1回で済む
query {
  getUser(id: "123") {
    name            # 名前だけ欲しい(住所やメールは不要)
    orderlist {     # 注文一覧も一緒に取得
      orderId
      orderingDate
    }
  }
}

エンドポイントは1つだけ(/graphql)。その代わり、「何が欲しいか」をクエリとして送ります。

REST と GraphQL の比較

観点 REST API GraphQL
エンドポイント リソースごとに複数(/users, /posts 1つ(/graphql
データ取得 サーバーが返す形が固定 クライアントが欲しい形を指定
複数リソース 複数回リクエスト 1回のクエリで取得可能
リアルタイム性 WebSocket を別途構築 Subscription でサポート

GraphQL は「REST の上位互換」ではなく、得意な場面が違います

  • GraphQL → 自分のアプリ内部(フロントエンドとバックエンドの間)で使うのが主流。クライアントがデータの形を自由に指定できる柔軟さは自社アプリ内では強力ですが、外部公開しづらい
  • REST API → 外部アプリケーションと連携するための API に向いている。汎用化することで複数のアプリケーションに対して個別に連携機能を開発する必要がなくなる。「誰でも同じエンドポイントを同じ形式で叩ける」シンプルさが強み

Amplify Gen 2 で AppSync(GraphQL)が採用されているのも、単独のアプリケーションとして開発する側面が強いからでしょうね。

GraphQL の3つの操作

GraphQL には3種類の操作があります。

操作 役割 利用例
Query データの取得(読み取り) 商品一覧を表示、ユーザー情報を取得
Mutation データの作成・更新・削除 注文を確定、お気に入りに追加
Subscription リアルタイム通知 在庫が変わったら画面に即反映

REST API だと「GET = 取得、POST = 作成、PUT = 更新、DELETE = 削除」とHTTPメソッドで使い分けますが、GraphQL は クエリの種類 で使い分けます。

AppSync とは

前置きが長くなりましたが、ここまでを踏まえ AWS AppSync について深堀りしていきます。

AppSync は、AWS が提供する フルマネージドの GraphQL API サービス です。ざっくり言うと、API Gateway の GraphQL 版 のような位置づけです。

通常 GraphQL を使う際は Apollo Server に代表されるAPIサーバーを自分で運用する必要がありますが、AppSync を使うことでお手軽に GraphQL を用いたアプリケーションを構築することができます。

AppSync の役割:3つの中継機能

AppSync は、クライアント(Web/Mobile)とデータソース群の に位置し、単なるリクエスト転送ではなく「スマートな中継」を行います。
AppSyncアーキテクチャ全体像.png

1. 認証・認可ゲートウェイ

リクエストがデータソースに到達する 前に 認証・認可チェックを行います。

例えば、フロントエンドとしてECサイトを構築することを考えてみます。

  • 認証(あなたは誰?):ログインしていないユーザーが注文履歴を見ようとしたら、そもそもリクエストを弾く
  • 認可(何ができる?):ログイン済みでも、他人の注文履歴は見られないようにする
# 商品一覧 → 誰でも見られる(認証不要)
query {
  listProducts {
    name
    price
    imageUrl
  }
}

# 注文履歴 → ログイン必須 & 本人のデータだけ返ってくる
query {
  listOrders {
    productName
    price
    status
    shippingAddress
  }
}
# ↑ owner ルールが設定されていると、AppSync が Cognito トークンから
#   ユーザーIDを取り出し「このユーザーが owner のレコードだけ返す」
#   というフィルタを裏側で自動適用してくれる

AppSync は Cognito 等のトークンに含まれるユーザーIDと、データの所有者(owner)を照合して「この人は本人だからOK」と判断します。本人でなければデータは返されません。

さらに、フィールドレベル で細かく制御することも可能です。たとえば「注文のステータスは管理者も見られるけど、配送先住所は本人だけ」といった設定もできます。

対応している認証方式は以下の通りです。

認証方式 ユースケース
Cognito User Pool 一般的なユーザーログイン(メール/パスワード、ソーシャルログイン)
API Key 公開データの読み取り、開発・テスト用途
IAM サーバー間通信、管理者用の内部 API
OIDC 外部の ID プロバイダー(Auth0 等)との連携
Lambda Authorizer 独自の認証ロジック(カスタムトークン検証など)

Amplify Gen 2 では認可ルールの設定も簡単に行えますが、実際にリクエスト時にトークンを検証してアクセスを許可/拒否しているのは AppSync です。

2. リクエストルーティング(交通整理)

認証・認可を通過したリクエストは、次に適切なデータソースへ振り分けられます。AppSync は GraphQL スキーマの各フィールドに対して「リゾルバ」をマッピングしており、フィールドごとに 適切なデータソースへ振り分けてくれます。

ECサイトのマイページのように、1つの画面にデータの保存先がそれぞれ異なる「ユーザー名」「注文情報」「おすすめ商品」を表示したいような場合に活躍します。

  • ユーザー情報 → DynamoDB の Users テーブル
  • 注文情報 → DynamoDB の Orders テーブル
  • おすすめ商品 → Lambda で動くレコメンドエンジン(外部 API やML モデルを使う)

このとき、以下のクエリを1回送るだけで、AppSync が裏側で3つの異なるデータソースに問い合わせてくれます。

query {
  getUser(id: "123") {    # → DynamoDB「Users テーブル」に問い合わせ
    name
    email
    orders {              # → DynamoDB「Orders テーブル」に問い合わせ
      items {
        productName
        price
      }
    }
    recommendations {     # → Lambda 関数(レコメンドエンジン)に問い合わせ
      title
      score
    }
  }
}

「どのフィールドがどのデータソースに行くか」は、AppSync の リゾルバ設定 で定義されています。

AppSyncルーティング.png

REST API だと /users/123/orders?userId=123/recommendations?userId=123 と3回リクエストが必要なところを、AppSync は 1回のリクエストで複数のデータソースからデータを集約 して返してくれるため、無駄のないデータ連携機能を構築できます。

3. データ変換ミドルウェア

リゾルバの中で、リクエストやレスポンスを 加工 できます。フロントエンドとデータソースの間で「翻訳」してくれるイメージです。

データソースから商品情報を取得する場面を考えてみます。

リクエスト変換:フロントエンドが送る形 → DynamoDB が理解する形

フロントエンドが送るもの:
  getProduct(id: "prod-001")

リゾルバが DynamoDB に送るもの:
  GetItem { TableName: "Products", Key: { id: { S: "prod-001" } } }

フロントエンドは DynamoDB のテーブル名や Key の形式を知る必要がありません。リゾルバが「翻訳」してくれます。

レスポンス変換:データソースの形 → フロントエンドが期待する形

DynamoDB が返すもの:
  { id: { S: "prod-001" }, price_jpy: { N: "1980" }, is_active: { BOOL: true } }

リゾルバがフロントエンドに返すもの:
  { id: "prod-001", price: 1980, isActive: true }

フィールド名の変換(price_jpyprice)や、DynamoDB 固有の型表記の除去を、リゾルバが吸収してくれます。

この「翻訳」の仕組みがあるおかげで、フロントエンドはデータソースの内部構造を知らなくて済みます。また、データソース側の構造を変更しても、リゾルバで吸収すればフロントエンド側の修正が不要になります。

AppSync のその他の機能

機能 説明
パイプラインリゾルバ 複数の処理ステップをチェーンして1つのリゾルバで実行できる
リアルタイム同期 Subscription で、データ変更を即座にクライアントへ通知
オフライン対応 クライアント側でデータをローカル保存し、復帰時に同期
サーバーサイドキャッシュ インメモリキャッシュでレイテンシを削減
自動スケーリング トラフィックに応じて自動でスケール

API Gateway との違い

類似サービスである API Gateway との違いについて、「3つの中継機能」の観点で比較してみました。

役割 AppSync API Gateway
認証・認可 Cognito / IAM / OIDC 等をネイティブサポート。フィールドレベルの認可も可能 Lambda Authorizer や IAM で実現。フィールドレベルの制御は自前で実装
ルーティング スキーマベース。フィールドごとに異なるデータソースへ振り分け URL パスベース。エンドポイントごとに Lambda 等を紐付け
データ変換 リゾルバで DynamoDB 等に直接アクセス&変換。Lambda 不要 基本的に Lambda 経由。マッピングテンプレートで簡易変換は可能

一番の違いは ルーティングの考え方 です。

  • API Gateway:「/users/123 に GET が来たから、この Lambda を呼ぼう」
  • AppSync:「getUsernameorders が要求されたから、それぞれのリゾルバを実行しよう」

AppSync の方が「フロントエンドが何を求めているか」をより細かく理解した上でルーティングしてくれます。

Amplify Gen 2 の役割

ここからは、Amplify Gen 2 が AppSync の機能をどう「簡単に使えるようにしているか」を見ていきます。Amplify Gen 2 では、TypeScript のスキーマ定義 にて複数の機能を自動生成してくれます。

何を書くか 何が自動生成されるか
TypeScriptのスキーマ定義 DynamoDB テーブル + AppSync API 一式
GraphQLスキーマの自動生成 GraphQL スキーマ(型・クエリ・ミューテーション)
認可ルールの設定 フィールド単位の認可ルール
リゾルバの振り分け先 CRUD リゾルバ(get / list / create / update / delete)

詳細は以降のセクションで説明します。

TypeScriptのスキーマ定義

Amplify Gen 2 の自動化レイヤーのベースとなるTypeScriptのスキーマ定義についてです。amplify/data/resource.ts にスキーマを定義するだけで、裏側で AppSync API が自動構築されます。

amplify/data/resource.ts
import { a, defineData, type ClientSchema } from '@aws-amplify/backend';

const schema = a.schema({
  // "Product" モデルを定義 → DynamoDB テーブル + CRUD リゾルバが自動生成される
  Product: a.model({
    name: a.string(),          // フィールド定義(GraphQL の型も自動生成)
    price: a.integer(),
    inStock: a.boolean()
  })
  .authorization(allow => [allow.owner()])  // 認可ルール:本人のみアクセス可
});

// フロントエンドで使う型を自動エクスポート(型安全なクライアント用)
export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'userPool',  // デフォルトの認証方式:Cognito User Pool
  }
});

たったこれだけで、AppSync GraphQL API + DynamoDB テーブル + CRUD リゾルバ + Subscription + 認可ルール + 型定義が全部生成されます。

GraphQLスキーマの自動生成

通常のAppSync では GraphQL を動かすために自分で .graphql ファイルに必要な GraphQLスキーマ を書く必要がありますが、Amplify Gen 2 ではビルド時の命名規約で、a.model() のモデル名から自動的に GraphQL のクエリ名が生成されます。

TypeScript で書くもの             →  自動生成される GraphQL
─────────────────────────────────────────────────────────
Product: a.model({...})          →  getProduct(id: ID!): Product
                                 →  listProducts(...): ProductConnection
                                 →  createProduct(input: ...): Product
                                 →  updateProduct(input: ...): Product
                                 →  deleteProduct(input: ...): Product

フロントエンドで client.models.Product.get({ id: "prod-001" }) と呼ぶと、裏では getProduct(id: "prod-001") という GraphQL クエリが AppSync に送られています。

認可ルールの設定

AppSync での認可ルールについても、Amplify Gen 2 ではスキーマ定義内で宣言的に書けます。

amplify/data/resource.ts
const schema = a.schema({
  // 商品情報 → 誰でも読める、作成・更新・削除は管理者だけ
  Product: a.model({
    name: a.string(),
    price: a.integer(),
  }).authorization(allow => [
    allow.guest().to(['read']),         // 未ログインでも閲覧OK
    allow.group('admin'),               // 管理者は全操作OK
  ]),

  // 注文情報 → 本人だけアクセス可能
  Order: a.model({
    productName: a.string(),
    price: a.integer(),
    status: a.string(),
  }).authorization(allow => [
    allow.owner(),                      // 本人のデータだけ見える
  ]),
});

.authorization(allow => [...]) と書くだけで、AppSync 側に認可ルールが自動設定されます。実際にリクエスト時にトークンを検証してアクセスを許可/拒否するのは AppSync の仕事です。

リゾルバの振り分け先

どのフィールドがどのデータソースに行くかの交通整理的な役割であるリゾルバについても、Amplify Gen 2 では、スキーマの書き方がそのままリゾルバの振り分け設定になります。

amplify/data/resource.ts
const schema = a.schema({
  User: a.model({           // ← model() → DynamoDB + リゾルバが自動生成
    name: a.string(),
    email: a.string(),
  }),
  Order: a.model({          // ← これも DynamoDB に自動マッピング
    productName: a.string(),
    price: a.integer(),
  }),
  // Lambda を使うカスタムクエリ
  recommendations: a.query()
    .returns(a.ref('Recommendation').array())
    .handler(a.handler.function('recommendEngine'))  // ← Lambda に振り分け
});
  • a.model() → DynamoDB + CRUD リゾルバが自動生成(Amplify の自動化)
  • a.handler.function() → Lambda に振り分け(明示的に指定)
  • a.handler.custom() → AppSync のリゾルバを直接書く

スキーマの書き方 = ルーティング設定 となり、振り分け設定を明確に管理できます。

裏側で何が起きているか

以上のようにスキーマ定義を設定することで、フロントエンドから Mutation のリクエストが入ると、裏側では図のように AppSync が働いてくれています。

AppSync裏側の仕組み.png

[Amplify の仕事]
  1. Amplify クライアントが GraphQL mutation を生成

[AppSync の仕事]
  2. AppSync が受信 → スキーマに対してバリデーション
  3. Cognito トークンで認証 → owner ルールで認可チェック
  4. リゾルバが DynamoDB に PutItem を実行
  5. 結果を GraphQL レスポンスとして返却
  6. Subscription 経由で他のクライアントにも通知

「意識しなくても動く」のが Amplify Gen 2 と AppSync 連携の良さですが、この流れを知っているとエラーが起きたときに「Amplify 側の問題か、AppSync 側の問題か」を切り分けやすくなります。

AppSync の限界 — 30秒の壁

便利な AppSync ですが、知っておくべき制約があります。

AppSync のリゾルバには タイムアウト30秒 という上限があります。リゾルバが30秒以内にレスポンスを返せないと、エラーになります。

これは API Gateway REST API(29秒)もほぼ同じで、AWS のリクエスト/レスポンス型サービスに共通する制約です。

30秒を超える処理はどうする?

たとえば以下のような処理は、30秒に収まらない可能性があります。

  • 大量データの一括処理(CSV インポートなど)
  • 外部 API の呼び出しが連鎖する処理
  • 動画のエンコードや画像の大量変換
  • AI/ML モデルの推論(大きなモデルの場合)

こういった場合は、非同期パターン が推奨されます。
AppSync非同期.png

[同期(30秒以内)]
  クライアント → AppSync → Lambda → 「ジョブを受け付けました」を即座に返す
                                  ↓
[非同期(時間制限なし)]
                              SQS / Step Functions → 重い処理を実行
                                  ↓
[完了通知]
                              AppSync Subscription で結果をクライアントに通知

ポイントは「受け付け」と「実行」を分離すること。AppSync のレスポンスは「ジョブID」だけ返して、実際の処理は SQS や Step Functions に任せます。処理が終わったら Subscription でクライアントに通知する、という流れです。

非同期処理では AppSync の Subscription(リアルタイム通知)が非常に有効です。非同期処理の完了を WebSocket でクライアントに即座に伝えられるので、ポーリング(定期的に問い合わせ)する必要がありません。
もちろん API Gateway でも WebSocket API を使えばリアルタイム通知は実現できますが、自分で構築する部分がかなり多くなるため、リアルタイム通知が必要なユースケースでは AppSync の良さが光ります。

まとめ

本記事では、AppSync が Amplify Gen 2 の裏側でどんな役割を果たしているかを「中継機能」という切り口で整理しました。

AppSync の3つの中継機能:

  • 認証・認可 — リクエストを通すか判断する門番
  • ルーティング — フィールドごとに適切なデータソースへ振り分け
  • データ変換 — フロントエンドとデータソースの間で「翻訳」

Amplify Gen 2 の役割:

  • TypeScript のスキーマ定義(amplify/data/resource.ts)から、AppSync の設定を自動生成する自動化レイヤー

AppSyncまとめ.png

今後 Amplify Gen 2 で凝ったアプリを作ろうとすると、処理時間の観点などで AppSync の動作も意識する必要が出てきそうなので、今理解を深められたことは良かったなと思います。

参考リンク

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?