Amazon Web Services ブログ

AWS Amplify と AWS CDK で GraphQL と DynamoDB に接続する

本日 (2023 年 10 月 4 日) 、Amplify の GraphQL API 機能のための AWS Cloud Development Kit (CDK) コンストラクト を発表できることを嬉しく思います。Amplify の GraphQL API CDK コンストラクトを使用すると、単一の GraphQL スキーマ定義を使用して、Amazon DynamoDB テーブルや AWS Lambda 関数などのデータソースをバックエンドとするリアルタイム GraphQL API を作成できます。(Construct Hub で見る)

フロントエンド用の API を立ち上げるためには、開発者は API エンドポイント、カスタムビジネスロジック、データソースを構築して繋げ合わせるために、何千行もの反復的で差別化されないコードを書く必要があります。AWS Amplify は、開発者が単一の定義ファイルでデータモデルを定義し、データソースの create, update, list, read, subscribe, delete のような一般的な API 操作をサポートするために必要な AWS リソースを自動的に生成できるようにすることで、この重労働を取り除きます。これまでは Amplify CLI でのみ利用可能でしたが、今回のアップデートでこの機能を AWS CDK に拡張します。

CDK Day 2023 の Amplify のセッションで、新しい Amplify GraphQL API コンストラクトのプレビューを皆さんにお見せしました。

新しい GraphQL API コンストラクトの詳細なツアーに参加しましょう!本記事では、フロントエンドのためのバックエンドを必要とし、AWS CDK を使っているお客様が利用できる 6 つの新機能にフォーカスします。

  1. 既存 CDK アプリやリソースとのシームレスな統合
  2. リアルタイム API とデータスタックのための信頼できる唯一の情報源
  3. 簡単に始められ、拡張できる認可ルール
  4. 拡張可能なカスタム Query, Mutation, Subscription API
  5. 生成されたリソースを完全に制御する L2 および L1 コンストラクトへのエスケープハッチ
  6. リアルタイム機能のためのファーストクラスのクライアントライブラリサポート

1. 既存 CDK アプリやリソースとのシームレスな統合

新しい Amplify GraphQL API CDK コンストラクトは、既存 CDK アプリ内のドロップインコンポーネントとして使用できます。Lambda 関数のような既存のリソースとシームレスに統合できます。CDK のコンポーザブルアーキテクチャをベースに、手作りのリソースやインポートしたリソースを GraphQL API のデータソースとして使用しながら、Amplify の自動化された CRUD 操作、認可ルール、AWS AppSync によるリアルタイムのサブスクリプションの恩恵を受けることができます。新しい Amplify GraphQL API コンストラクトでは、CDK で開始し、CDK で反復し、CDK でデプロイします。

開始するには、既存 CDK アプリを使用するか、新しいアプリを作成します。

mkdir amplify-cdk-demo
cd amplify-cdk-demo
mkdir backend
cd backend
cdk init app --language=typescript

次に、以下のコマンドで新しい Amplify GraphQL API CDK コンストラクトと依存関係をインストールします。

npm install @aws-amplify/graphql-api-construct

CDK アプリの lib/backend-stack.ts ファイルで、新しい Amplify GraphQL API コンストラクトをインポートして初期化します。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { AmplifyGraphqlApi, AmplifyGraphqlDefinition } from '@aws-amplify/graphql-api-construct';
import * as path from 'path'

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const amplifyApi = new AmplifyGraphqlApi(this, "MyNewApi", {
      definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")),
      authorizationModes: {
        defaultAuthorizationMode: 'API_KEY',
        apiKeyConfig: {
          expires: cdk.Duration.days(30)
        }
      }
    })
  }
}

上記のコードは、schema.graphql に格納されたスキーマ定義に基づいて新しい API をインスタンス化し、API キーを API のデフォルト認証モードとして使用します。API キーの有効期限はデプロイ時から 30 日間です。

次に、schema.graphql を新規作成し、データモデルと API の単一のソースとして使用します。

2. リアルタイム API とデータスタックのための信頼できる唯一の情報源

新しい Amplify GraphQL API コンストラクトでは、開発者はデータモデルを GraphQL スキーマ定義言語で定義し、DynamoDB テーブル (@model) 、Lambda 関数 (@function) 、OpenSearch クラスタ (@searchable) などの付随するデータソースを生成するためのディレクティブを使って拡張します。CDK コンストラクトは、Amplify CLI の既存の GraphQL Transformer 機能と完全に同等の機能を備えています。開発者は @auth ディレクティブを使用して API とデータをセキュアにすることができます。@auth ディレクティブは、グローバル、モデルレベル、フィールドレベルの認可ルールを構成する機能だけでなく、デフォルトで拒否する認可を提供します。新しい CDK コンストラクトは、CDK コード内から Amplify によって生成されたすべてのリソースにアクセスし、カスタマイズする機能を備えており、拡張可能です。

以下のスキーマは Blog アプリケーションを記述しています。以下の GraphQL スキーマをアプリケーションにコピー & ペーストしてください。

type Blog @model @auth(rules: [{ allow: public }]) {
  title: String
  content: String
  authors: [String]
}

次に、CDK を使ってアプリケーションをデプロイし、プロンプトが表示されたら “y” と答えます。

cdk deploy

デプロイが完了したら、AWS AppSync コンソールにアクセスして API を選択し、いくつかのテストクエリを実行します。作成クエリとリストクエリを実行して結果を確認してみましょう。

3. 簡単に始められ、拡張できる認可ルール

新しい Amplify GraphQL API CDK コンストラクトは、デフォルトの拒否ルールをすぐに提供することで、認可を簡単に始めることができます。API レベル、データモデルごと、あるいは個々のフィールドごとにきめ細かい認証ルールを追加することで、アクセス制御をさらにカスタマイズすることができます。Amplify は、ユーザー単位、複数ユーザー、グループ単位、複数グループによる特定のレコードへのアクセスなど、一般的な認証ルールを提供します。これらのルールは、Amazon Cognito または任意の OpenID Connect (OIDC) プロバイダで動作します。カスタムの認可パターンを実現するために、Lambda 関数を活用することもできます。この宣言的な認証モデルを使えば、アプリケーションの成長に合わせて拡張できる、堅牢なアクセス制御を得ることができます。

API をロックダウンして、一般ユーザーは全てのブログを読むことができるが、サインインしたユーザーはブログの作成、閲覧、更新、削除ができるようにしてみよう。まず、GraphQL スキーマを更新して、”public ” アクセスルールをスコープダウンし、新しい “owner” 認可ルールを追加しましょう。”owner” 認可ルールでは、ユーザーごとの認可を指定することができる。サインインしたユーザーが新しいレコードを作成すると、そのレコードは自動的にサインインしたユーザーをオーナーとして指定します。

type Blog @model @auth(rules: [
  { allow: public, operations: [ read ] },
  { allow: owner }
]) {
  title: String
  content: String
  authors: [String]
}

認可ルールは以下のように定義されています。

  • Public (API キーを使用するユーザー ) はどのブログでも読むことができます。
  • Owner (Cognito 経由でサインインしたユーザー) は自分のブログを作成、閲覧、更新、削除できます。

注意:グループベースの認可やフィールドレベルの認可など、さらに高度な認可を追加できます。これによって、「Admins グループのメンバーにのみブログの削除を許可する」、「サインインした作者にのみ表示される新しい privateNotes フィールドを追加する」などのユースケースに拡張することが出来ます。認可機能の全範囲については、ドキュメントを参照してください。

lib/backend-stack.ts の CDK コンストラクトプロパティを更新し、ユーザーのサインインとサインアップ管理に新しいユーザープールまたは既存のユーザープールを使用するようにしました。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { AmplifyGraphqlApi, AmplifyGraphqlDefinition } from '@aws-amplify/graphql-api-construct';
import * as path from 'path'
import { UserPool, UserPoolClient } from 'aws-cdk-lib/aws-cognito';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const userPool = new UserPool(this, "MyNewUserPool")
    new UserPoolClient(this, "MyNewUserPoolClient", { userPool: userPool })

    const amplifyApi = new AmplifyGraphqlApi(this, "MyNewApi", {
      definition: AmplifyGraphqlDefinition.fromFiles(path.join(__dirname, "schema.graphql")),
      authorizationModes: {
        defaultAuthorizationMode: 'API_KEY',
        apiKeyConfig: {
          expires: cdk.Duration.days(30)
        },
        userPoolConfig: {
          userPool: userPool
        }
      }
    })
  }
}

以下のコマンドで最新のものを再度デプロイします。

cdk deploy

デプロイ後、Amazon Cognito ユーザプールコンソールでテストユーザーを作成します。

AppSync コンソールでは API キーを使う、もしくはサインインしたユーザーとして、GraphQL クエリを実行してテストすることができます。

4. 拡張可能なカスタム Query, Mutation, Subscription API

基本的な CRUD 操作以外のカスタムビジネスロジックを実装する必要がありますか?Amplify GraphQL API CDK コンストラクトを使用することで、独自の Lambda 関数をトリガーするカスタム GraphQL リゾルバを簡単に定義できます。カスタム Query, Mutation, Subscription を追加して、検索、分析、メッセージングなどの特殊な API を実装します。カスタムデータタイプを定義して、レスポンスフォーマットを構造化します。RDS や 3rd Party のAPI など、あらゆるソースからデータにアクセスします。Amplify が GraphQL スキーマの生成、クライアントSDK の構築、およびそれらの繋込みを行うことで、お客様はカスタムロジックの作成に専念することが出来ます。GraphQL と AWS Lambda の柔軟性で API 機能を拡張しましょう。

例えば、ブログアプリケーションにシンプルな PubSub API 機能を構築するとします。読者はブログサイトに表示され、お互いにメッセージをライブ配信することができます。

まず、GraphQL スキーマを編集して、サインインしたユーザがメッセージを送信し、Subscription がメッセージを受信できるような Mutation を追加する必要があります。

type Blog @model @auth(rules: [
  { allow: public, operations: [ read ] },
  { allow: owner }
]) {
  title: String
  content: String
  authors: [String]
}

type Mutation {
  broadcastLiveMessage(message: String): String
}

type Subscription {
  subscribeToLiveMessages: String @aws_subscribe(mutations: ["broadcastLiveMessage"])
}

@aws_subscribe ディレクティブは、 mutations 引数で指定されたすべての Mutations に対してリアルタイムの Subscription をセットアップします。

次に、lib/backend-stack.ts の CDK コードに戻って、broadcastLiveMessage Mutation からのメッセージを subscribeToLiveMessages Subscription に渡す JavaScript リゾルバを追加します。Amplify GraphQL API をインスタンス化した後、以下のコードを追加します。

    const broadcastDataSource = amplifyApi.addNoneDataSource("BroadcastNone")
    amplifyApi.addResolver("BroadcastResolver", {
      dataSource: broadcastDataSource,
      typeName: 'Mutation',
      fieldName: 'broadcastLiveMessage',
      code: Code.fromAsset(path.join(__dirname, 'resolvers', 'broadcastLiveMessage.js')),
      runtime: FunctionRuntime.JS_1_0_0
    })

上記のコードでは、まず NONE データソースを作成し、API に新しいリゾルバを追加してbroadcastLiveMessage Mutation を処理します。このタイプのデータソースは、アクションを他の AWSリソースと連携することなく、AppSync 内でローカルに解決したい場合に使用します。

broadcastLiveMessage Mutations を処理するために、新しい resolvers フォルダに broadcastLiveMessage.js を作成します。resolvers/broadcastLiveMessage.js は、Mutation からのメッセージ引数を使用し、Subscription に結果としてそれを渡します。

export function request(ctx) {
  return {
    payload: {
      message: ctx.arguments.message
    }
  }
}

export function response(ctx) {
  return ctx.result.message
}

もう一度変更をデプロイしてみます。

cdk deploy

それでは、2 つの AppSync コンソールウィンドウを開いて、変更を検証してみましょう。1 つは一般向けの Subscription で、もう 1 つは Mutations を送信するために使用します。

5. 生成されたリソースを完全に制御する L2 および L1 コンストラクトへのエスケープハッチ

Amplify は、DynamoDB テーブルや Lambda 関数のような基本リソースのプロビジョニングをしますが、アプリケーションが成熟するにつれて、開発者はより深いアクセスが必要になってきます。CDK コンストラクトは、L2 および L1 CDK コンストラクトを通じて基礎となるリソースに直接アクセスするためのエスケープハッチを提供します。DynamoDB の課金モードを微調整したり、Lambda に VPC インタフェースを追加したり、OpenSearch インデックスを CDK でカスタマイズできます。

AppSync API や DynamoDB テーブルなど、生成されたすべてのリソースは、L2 コンストラクトとして.resources パラメータの下で利用可能です。.resources.cfnResources 経由でアクセスすることで、生成されたリソースの L1 コンストラクトにさらにドロップダウンできます。例えば、基礎となる AppSync API で X-Ray トレースを有効にするには、L1 レベルにドロップダウンして必要な X-Ray トレースを設定します。

>amplifyApi.resources.cfnResources.cfnGraphqlApi.xrayEnabled = true

一度設定すれば、変更を再度デプロイすることができます。

cdk deploy

6. リアルタイム機能のためのファーストクラスのクライアントライブラリサポート

バックエンドの実装を合理化すると同時に、Amplify はお客様の GraphQL API に強く型付けされたクライアント SDK の自動生成を提供します。クライアントアプリでリアルタイムデータのサポートと強力な GraphQL クエリをすぐに利用できます。Web アプリの場合は、React ベースの JavaScript クライアントを生成します。クロスプラットフォームまたはモバイルアプリについては、Android、iOS、および React Native をサポートしています。これらのプラットフォームに対応するクライアントコードを生成する方法については、ドキュメントを参照してください。Amplify クライアントライブラリを使えば、複雑なロジックを 1 からコーディングすることなく、魅力的なユーザー体験を構築することができます。

この例では、React アプリケーションを使ってこのライブブログを紹介します。まず、ターミナルから以下のコマンドを実行して、新しい React アプリケーションを作成します。

cd ..
npx create-react-app frontendcd frontend

全体的なフォルダー構造は以下のようになります。

>amplify-cdk-demo
|-backend
|-frontend

次に、アプリケーションをバックエンド API に接続するための Amplify Libraries をインストールします。

npm install aws-amplify

次に、バックエンド API を認識するように Amplify Libraries を設定する必要があります。アプリのエントリーポイント (すなわち index.js) に行き、cdk deploy を実行したときにターミナルに出力された API エンドポイントの情報を使って、Amplify Libraries を設定します。先程の cdk deploy で以下のように出力されているはずです。

✨  Deployment time: 62.86s

Outputs:
BackendStack.amplifyApiModelSchemaS3Uri = s3://backendstack-mynewapiamplifycodegenassetsamplifyc-1u3xykyhe309m/model-schema.graphql
BackendStack.awsAppsyncApiEndpoint = https://wy5mtp7jzfctxc5w5pzkcoktbi.appsync-api.us-east-1.amazonaws.com/graphql
BackendStack.awsAppsyncApiId = eci46vifpvbvhno55uo2ovtoqm
BackendStack.awsAppsyncApiKey = da2-XXXX
BackendStack.awsAppsyncAuthenticationType = API_KEY
BackendStack.awsAppsyncRegion = us-east-1

フロントエンドの index.js に、Amplify Libraries をインポートし、対応する情報を設定します。

import { Amplify } from 'aws-amplify'

Amplify.configure({
  aws_appsync_graphqlEndpoint: 'https://wy5mtp7jzfctxc5w5pzkcoktbi.appsync-api.us-east-1.amazonaws.com/graphql',
  aws_appsync_region: 'us-east-1',
  aws_appsync_authenticationType: 'API_KEY',
  aws_appsync_apiKey: 'da2-XXXX'
})

Amplify Libraries の API カテゴリを使って生の GraphQL リクエストを書くこともできるます、以下の npx スクリプトを使うことで、一般的なリクエストの大部分を Amplify に生成させることもできます。

npx @aws-amplify/cli codegen add --apiId eci46vifpvbvhno55uo2ovtoqm --region us-east-1
npx @aws-amplify/cli codegen

注意:バックエンドにスキーマ変更をデプロイする度に、対応するクライアントヘルパーコードを再生成するために、npx @aws-amplify/cli codegen を再実行する必要があります。

src/graphql/ フォルダに新しいファイル群があるはずです。これらは GraphQL Query、Mutation、Subscription のクライアントコードヘルパーです。

src/graphql/
├── mutations.js
├── queries.js
└── subscriptions.js

ブログとメッセージ機能を表示するためにフロントエンドの UI を変更します。App.js にアクセスし、以下の内容に置き換えます。

import "./App.css";
import { useEffect, useState } from "react";
import { API } from "aws-amplify";
import { listBlogs } from "./graphql/queries";
import { subscribeToLiveMessages } from "./graphql/subscriptions";
import { broadcastLiveMessage } from "./graphql/mutations";

function App() {
  const [blogs, setBlogs] = useState([]);
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // fetches all blog posts
    async function fetchBlogs() {
      const response = await API.graphql({
        query: listBlogs,
      });
      setBlogs(response.data.listBlogs.items);
    }
    fetchBlogs();

    // setup subscriptions for live chat messages
    const subscription = API.graphql({
      query: subscribeToLiveMessages
    }).subscribe(next => {
      setMessages(messages => [...messages, next.value.data.subscribeToLiveMessages])
    })

    return () => subscription.unsubscribe()
  }, []);

  // sends the live chat message to users
  function handleMessageSend(event) {
    if (event.key === 'Enter') {
      API.graphql({
        query: broadcastLiveMessage,
        variables: {
          message: event.target.value
        }
      })
    }
  }

  return (
    <div style={{ display: "flex", gap: 20 }}>
      <div>
        <h1>Articles</h1>
        {blogs.map((blog) => (
          <div style={{ border: '1px solid black', padding: 10, borderRadius: 10}}>
            <h2>{blog.title}</h2>
            <p>{blog.content}</p>
          </div>
        ))}
      </div>
      <div>
        <h1>Live chat</h1>
        <input type="text" placeholder="Hit enter to send message" onKeyDown={handleMessageSend} />
        <hr></hr>
        <ul>
          {messages.map(message => <li>{message}</li>)}
        </ul>
      </div>
    </div>
  );
}

export default App;

70 行以下のコードで、ライブチャット機能を持つブログ記事フロントエンドを構築しました。アプリをローカルで実行するには、ターミナルで以下のコマンドを実行してます。

npm run start

あなたのアプリはこのように見えるはずです。ライブチャット機能をテストするために、別のウィンドウで開いてみてください。

🥳 成功の秘訣

CDK を使用してデプロイされたリアルタイム API とデータスタックが React アプリに統合されました!これは、Amplify GraphQL CDK コンストラクトのほんの一部の機能です。より深く掘り下げるために、以下のリソースもぜひご覧ください。

他に質問がある場合は、Discord に参加するか、問題や機能要求を提出したい場合は、GitHub にアクセスしてください。

本記事は、「Connect a React app to GraphQL and DynamoDB with AWS CDK and Amplify」を翻訳したものです。

翻訳者について

稲田 大陸

AWS Japan で働く筋トレが趣味のソリューションアーキテクト。普段は製造業のお客様を中心に技術支援を行っています。好きな AWS サービスは Amazon Location Service と AWS Amplify で、日本のお客様向けに Amazon Location Service の解説ブログなどを執筆しています。