Amazon Web Services ブログ

AWS CDKまたはCloudFormationを使用し、カスタムAWSリソースでAmplifyバックエンドを拡張する新機能「カスタム」のご紹介

この記事は、Extend Amplify backend with custom AWS resources using AWS CDK or CloudFormation を翻訳したものです。

AWS AmplifyでAWS Cloud Development Kit(CDK)またはAWS CloudFormationを使用し、Amplifyが作成したバックエンドに175以上のAWSサービスを追加する新しいコマンドamplify add customが発表されました。カスタムリソースを追加する新しい機能により、開発者は1つのコマンドでAmplifyに組み込み済みのユースケース以外にリソースを追加できます。

AWS Amplifyは、AWS上でモバイルアプリやウェブアプリを構築するための最も速くて簡単な手段を提供するサービスです。 Amplifyは、フロントエンドのウェブ開発者とモバイル開発者がAWSサービスの力を活用して、革新的で機能豊富なアプリケーションを構築できるようにする一連のツールとサービスで構成されています。 AWS Amplify CLIは、フロントエンド開発者がクラウドでアプリバックエンドを簡単に作成できるコマンドラインツールチェーンです。

この記事で学習する内容

  • カスタムAWSリソースをAmplifyプロジェクトに追加する方法
  • カスタムAWSリソースをAmplifyのマルチ環境ワークフローと互換性のあるものにする方法
  • Lambda関数からカスタムAWSリソースにアクセスする方法

この記事を通して構築するもの:

  • 友人と共有して全ての材料を追加できるディナーパーティーのショッピングリストアプリ
  • ショッピングの準備に役立つように、Amazon SNSを使用して必要な全ての材料を含む要約メールを受信する機能

前提条件:

  • 最新のAmplify CLIをインストールされていること。バージョン7以降が必要です。
    • インストールする場合はターミナルを開き、npm i -g @aws-amplify/cliを実行します。
  • Amplify CLIがすでに設定されていること。
    • Amplify CLIがまだ設定されていない場合は、ドキュメントページのこちらのガイドにしたがって設定してください。

1. ReactとAmplifyプロジェクトを初期化する

まず、新しいディレクトリを作成し、Amplifyプロジェクトを初期化します。

npx create-react-app amplified-shopping
cd amplified-shopping
amplify init -y

Amplifyプロジェクトは、アプリのバックエンドを開発するための出発点です。 Amplifyプロジェクトが初期化された後、amplify add apiを介して、Amazon DynamoDBによってサポートされるGraphQL APIなどのバックエンドリソースを簡単に追加できます。

2. ショッピングリストアイテムを保存するようにアプリデータモデルを構成する

次に、ショッピングリストのデータをどこかに保存する必要があります。 Amplify CLIは、GraphQL APIとその基礎となるリソースの作成に役立つ「APIカテゴリー」を提供します。amplify add apiを実行して、GraphQL APIを作成します。

amplify add api

(このデモは全てデフォルトの設定で進めることができます。)

CLIを実行すると、GraphQLスキーマを編集するように要求されます。GraphQLスキーマはデータモデルを定義するため、Amplifyが生成する基盤となるインフラストラクチャも定義します。

アプリのschema.graphqlファイルには以下のデータモデルを使用します。

type ShoppingItem @model { #Creates a database for ShoppingItem 
  id: ID!
  ingredient: String
  quantity: Float
  unit: String
}

type Mutation {
  sendSummaryEmail: Boolean @function(name: "sendSummary-${env}")
}

@modelディレクティブを使用してtypeを指定すると、そのtypeのフィールドをサポートするDynamoDBテーブルが作成されます。上記の場合ShoppingItemテーブルは、ID、ingredient、quantity、unitで作成されます。 @functionディレクティブを使用すると、GraphQL APIリクエストを処理するLambda関数に再ルーティングできます。後でこのLambda関数を使用して、メッセージをAmazon SNSにpublishし、メール通知をトリガーします。

3. AmazonSNSトピックをカスタムAWSリソースとしてAmplifyプロジェクトに追加する

ここまでの手順でバックエンドのデータベースがセットアップされました。Amazon SNSトピックを作成してメールサブスクリプションを利用しましょう。Amazon SNSトピックはメッセージを受信するたびに、事前に指定されたメールアドレスにメッセージを転送します。

現在、Amazon SNSトピックの追加はAmplifyに組み込まれていませんが、以下の手順を実行してカスタムAWSリソースを追加できるようになりました。

amplify add custom

このアプリでは、AWS CDKを使用してカスタムAWSリソースを定義します。ただし、これはCloudFormationでも簡単に定義できます。 Amplify CLIは、すべてのカスタムリソース定義を含む新しいcdk-stack.tsファイルを開きます。

cdk-stack.tsファイルを編集し、Amazon SNSトピックとメールサブスクリプションを作成しましょう。

(注:以下のコードスニペットの”<YOUR_EMAIL_ADDRESS_HERE>”を必ず置き換えてください)

import * as cdk from '@aws-cdk/core';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import * as sns from '@aws-cdk/aws-sns';
import * as subs from '@aws-cdk/aws-sns-subscriptions';

export class cdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
    super(scope, id, props);
    /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
    new cdk.CfnParameter(this, 'env', {
      type: 'String',
      description: 'Current Amplify CLI env name',
    });
    
    // Create the SNS topic 
    const topic = new sns.Topic(this, 'sns-topic', {
      // Reference the Amplify env to ensure multi-env workflow function correctly
      topicName: `sns-topic-${AmplifyHelpers.getProjectInfo().projectName}-${cdk.Fn.ref('env')}`
    });

    // Add an email subscription
    topic.addSubscription(new subs.EmailSubscription("<YOUR_EMAIL_ADDRESS_HERE>"));
  }
}

この時点で、次のコマンドを実行してアプリのバックエンドのデプロイを試みることができます。

amplify push -y

新しいメールサブスクリプションが設定されると、後続のメッセージを受信するためにメール受信者は「Confirm subscription」をする必要があります。メールを確認して「Confirm subscription」をクリックしてください。

4.すべてを接続するLambda関数を構成する

ここまで、メール通知のセットアップとデータベースの構成が完了しました。あとは全てを繋ぎ合わせるようにLambda関数を設定するだけです。

amplify add function

GraphQL APIのミューテーションにアクセスできるNode.js関数を作成するには、以下の選択肢を必ず選択してください。

※GraphQLスキーマで指定されている関数名「sendSummary」を使用してください。

? Select which capability you want to add:
> Lambda function (serverless function)
? Provide an AWS Lambda function name:
> sendSummary
? Choose the runtime that you want to use:
> NodeJS
? Choose the function template that you want to use:
> Hello World
? Do you want to configure advanced settings?
> Yes
? Do you want to access other resources in this project from your Lambda function?
> Yes
? Select the categories you want this function to have access to.
> api
? Select the operations you want to permit on amplifiedshopping
> Mutation

(以降の選択肢は全てデフォルトのものを選択して進めてください)

CLIフローの最後で関数コードを編集できるようになります。Lambda関数のロジックを編集する前に、ディレクトリに移動してnode-fetchのパッケージを追加し、GraphQL APIを簡単に呼び出せるようにします。

cd amplify/backend/function/sendSummary/src
npm install node-fetch@2

以下のコードスニペットを追加します。

  1. 全てのショッピングアイテムを取得する
  2. メールのメッセージをフォーマットする
  3. Amazon SNS経由でメール通知を送信する
/* Amplify Params - DO NOT EDIT
    API_AMPLIFIEDSHOPPING_GRAPHQLAPIENDPOINTOUTPUT
    API_AMPLIFIEDSHOPPING_GRAPHQLAPIIDOUTPUT
    API_AMPLIFIEDSHOPPING_GRAPHQLAPIKEYOUTPUT
    ENV
    REGION
Amplify Params - DO NOT EDIT */
const fetch = require("node-fetch")
const AWS = require('aws-sdk')
const sns = new AWS.SNS()
const graphqlQuery = `query listShoppingItems {
    listShoppingItems {
        items {
            id
            ingredient
            quantity
            unit
        }
    }
}
`

exports.handler = async (event) => {
    const response = await fetch(process.env.API_AMPLIFIEDSHOPPING_GRAPHQLAPIENDPOINTOUTPUT, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "x-api-key": process.env.API_AMPLIFIEDSHOPPING_GRAPHQLAPIKEYOUTPUT
        },
        body: JSON.stringify({
            query: graphqlQuery,
            operationName: "listShoppingItems",
        })
    })

    const result = await response.json()
    const shoppingItems = result.data.listShoppingItems.items

    await sns.publish({
        // For demo purposes hard-coded, normally recommended to use environment variable
        TopicArn: "<YOUR-SNS-TOPIC-ARN-HERE>",
        Message: `Here's shopping cart summary - ${new Date().toDateString()}:\n` +
            `${shoppingItems.map(item => `${item.quantity} ${item.unit} - ${item.ingredient}`)
                .join('\n')}`
    }).promise().catch(e => console.log(e))

    return true
};

“<YOUR-SNS-TOPIC-ARN-HERE>”をAmazon SNSトピックARNに上書きしたことを確認してください。Amazon SNSトピックARNはサブスクリプション確認メールのすぐ下の「You have chosen to subscribe to the topic:」に、以下のように記述されています。

arn:aws:sns:<your-region>:<your-aws-account-id>:sns-topic-amplifiedshopping-dev

次にLambda関数がこのAmazon SNSトピックのPublishアクションにアクセスできるように、カスタムIAMポリシーを追加する必要があります。Lambda関数フォルダーamplify/backend/function/sendSummary/にはcustom-policies.jsonがあります。このファイルに追加するカスタムIAMポリシーはここで定義できます。Amazon SNSトピックにメッセージを公開するためのアクセスを提供するには、custom-policies.jsonファイルを次のように置き換えます。

[
  {
    "Action": ["sns:Publish"],
    "Resource": ["arn:aws:sns:*:*:sns-topic-amplifiedshopping-${env}"]
  }
]

全ての設定が完了したので、バックエンドをAWS上にデプロイします。

amplify push -y

5.フロントエンドを構築する

まず、プロジェクトに必要な全てのパッケージをインストールする必要があります。

Reactアプリのルートディレクトリに移動して、次のコマンドを実行します。

npm install aws-amplify recipe-ingredient-parser-v3

これにより、Amplifyライブラリがインストールされ、アプリがAWS上のバックエンドに接続できるようになります。さらに、自由形式のテキストから構造化された構成要素のデータを抽出するために、オープンソースの言語パーサーライブラリを使用します。例えばsix cups of milk{ingredient: "milk", quantity: 6, unit: "cup"}に変換します。

それでは、ReactアプリでAmplifyを設定して、AWS上のバックエンドに接続できるように設定します。index.jsファイルに移動し、ReactDOM.render(...)の直前に次の3行のコードを追加します。

import Amplify from 'aws-amplify'
import awsconfig from './aws-exports'

Amplify.configure(awsconfig)

次にApp.jsを以下の内容に置き換えます。

import { useEffect, useState } from 'react';
import { API } from 'aws-amplify';
import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import { parse } from 'recipe-ingredient-parser-v3';


function App() {
  const [ingredient, setIngredient] = useState()
  const [shoppingItems, setShoppingItems] = useState([])
  const fetchShoppingList = async () => {
    const response = await API.graphql({ query: queries.listShoppingItems })
    setShoppingItems(response.data.listShoppingItems.items)
  }

  useEffect(() => {
    fetchShoppingList()
  }, [])

  return (
    <div className="App">
      <input value={ingredient} onChange={e => setIngredient(e.target.value)}/>
      <button onClick={async () => {
        const parsed = parse(ingredient, 'eng')
        console.log(parsed)
        if (parsed.unit && parsed.ingredient && parsed.quantity) {
          setIngredient("")
          await API.graphql({
            query: mutations.createShoppingItem,
            variables: {
              input: {
                ingredient: parsed.ingredient,
                quantity: parsed.quantity,
                unit: parsed.unit
              }
            }
          })
          fetchShoppingList()
        }
      }}>Add ingredient</button>
      <h1>Ingredients</h1>
      <button onClick={fetchShoppingList}>Refresh</button>
      <button onClick={async () => {
        await API.graphql({
          query: mutations.sendSummaryEmail
        })
      }}>Send summary email</button>
      <ul>
        {shoppingItems.map(({ ingredient, quantity, unit }) => <li>
          <div>{quantity} {unit} - {ingredient}</div>
        </li>)}
      </ul>
    </div>
  );
}

export default App;

次に以下のコマンドを実行してアプリをテストします。

yarn start

ディナーパーティーのための材料をいくつか追加してみてください!次に「send summary email」をクリックして、受信トレイを確認してください。

🥳完成!

このブログ記事では、AWS AmplifyのAPIと関数の機能カテゴリを使用してフルスタックアプリを構築する方法を学習しました。また、未だAmplifyに組み込まれていないカスタムAWSリソースを追加する方法も学習しました。

このアプリを本番環境へ持っていくには何が必要でしょうか?まず最も重要なこととして、GraphQLスキーマの認証ルールを設定する必要があります。さらに、IAMを使用してGraphQL APIに対してLambda関数を認証する必要もあります。最後になりますが、メール受信者を追加する仕組みも導入する必要があります。

詳細については、以下のドキュメントを確認してください。

フィードバックがあればGitHubに共有するか、Discordのコミュニティに参加してください!

翻訳はGaming Solutions ArchitectのShintaro Watanabeが担当し、Developer Relations Engineer(Mobile/Web)のDaijiro WachiとPrototyping EngineerのTomohiro Tsukuiが監訳しました。