😇

インフラよく分かってない奴がAmplify Gen2を使ってみた

2025/01/12に公開

はじめに

私は実務でバックエンドの実装に関わることが多いのですが、インフラは昔から苦手で、インフラが絡むような場面では、インフラエンジニアに頼り切っていて敬遠していました。AWSに関しては、簡単なVPCやEC2インスタンスを構築した経験がある程度で、CDKに至っては全くの初心者です。

そんな中、最近Amplify Gen2を利用する案件に携わることになりました。直接実装に関わっていないのですが、話に聞くとAmplify Gen2は、TypeScriptで簡単にバックエンドを構築できるサービスらしく、実際にどのようなものなのか自分自身の目で確かめてみたいと思うようになりました。

そこで「インフラはイマイチよく分かってないけど、Amplify Gen2って本当に簡単に使えるの?」という疑問を解消すべく、実際にAmplify Gen2を使ってシンプルなチャットアプリを作成し、デプロイしてみることにしました!

この記事では、AWSもCDKもイマイチ分かってない状態から、Amplify Gen2を使ってシンプルなチャットアプリを作成し、デプロイするまでの過程を記録します。

準備するもの

プロジェクトのセットアップを始める前に用意すべきものについて説明します。

1. AWSアカウント

  • まだ持っていない方は、AWS公式サイトから作成してください。
  • Amplifyのsandbox実行で必要なIAMユーザーをAdministratorAccess権限を付与して作成しておいてください(本当は最小権限の方が良いらしいが、今回は簡略化のためAdministratorAccessを付与します)

2. Node.js + pnpm

  • インストールされているかターミナルでnode -vと実行して確認してください。
  • インストールされていない場合は、公式サイトからダウンロードしてインストールしてください。
  • 今回パッケージマネージャーにpnpmを利用するので、Node.jsをインストールした後に、公式サイトを参考にインストールしてください。

3. AWS CLI

  • インストールされているかターミナルでaws --versionと実行して確認してください。
  • インストールされていない場合は、公式ドキュメントを参考にインストールしてください。

Amplify Gen2プロジェクトのセットアップ

Amplify Gen2プロジェクトのセットアップ手順について説明します。

Amplifyには、Next.jsやNuxt.jsなどで簡単にプロジェクトを作成するためのテンプレートが用意されており、そちらを利用しても良いのですが、今回はせっかくなのでマニュアルインストールでプロジェクトをセットアップしていきます。

リポジトリ

今回開発したアプリのリポジトリはこちらです。
https://github.com/takemo101/amplify-simple-message

Next.jsのセットアップ

まずは、Next.jsのプロジェクトを作成します。
pnpmを使う場合は、以下のコマンドでプロジェクトを作成できます。

pnpm create next-app

質問形式の回答は以下のとおりです。

✔ What is your project named? … amplify-simple-message # 好きなプロジェクト名入力
✔ Would you like to use TypeScript? … No / Yes # Yesで回答
✔ Would you like to use ESLint? … No / Yes # Noで回答
✔ Would you like to use Tailwind CSS? … No / Yes # Yesで回答
✔ Would you like to use App Router? (recommended) … No / Yes # Yesで回答
✔ Would you like to use Turbopack for `next dev`? … No / Yes # Noで回答
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes # Yesで回答
✔ What import alias would you like configured? › @/* # 何も入力せずEnter

これで、Next.jsでのフロント開発の準備が整いました!

今回はLintやFomatterに関してはBiome.jsを利用しているので、ESLintはNoで回答しています。

Amplifyのセットアップ

次に、プロジェクトに移動してAmplifyでバックエンドを開発するパッケージなどをインストールしていきます。
まずは、Amplifyで認証・データモデル・Lambda関数・CDKなどを扱うのに必要なパッケージをインストールします。

pnpm add --save-dev @aws-amplify/backend @aws-amplify/backend-cli @types/aws-lambda aws-cdk aws-cdk-lib constructs esbuild tsx
pnpm add aws-amplify @aws-amplify/adapter-nextjs @aws-amplify/ui-react
# 今回Redisを利用するので``ioredis``をインストール
pnpm add ioredis

必要なパッケージは揃ったので、Amplifyのデプロイや実装に必要なファイルを、以下のNext.jsのテンプレートからコピーしてきます。
https://github.com/aws-samples/amplify-next-template/tree/main/amplify

以下のファイルをコピーしてください。

  • ./amplifyディレクトリ丸ごと
  • amplify.yml

tsconfig.jsonは以下のように編集しました。

tsconfig.json
{
  "compilerOptions": {
    // ... 省略
    "paths": {
      "~/*": ["./*"],
      "@/*": ["./src/*"],
      "@amplify/*": ["./amplify/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  // amplifyディレクトリは除外しておく
  "exclude": ["node_modules", "amplify"]
}

pathsオプションで@amplify/*のパスエイリアスを設定することで、./amplifyディレクトリ内のファイルを簡単にインポートできるようになります。

amplify.ymlは以下のように編集しました。

amplify.yaml
version: 1
backend:
  phases:
    preBuild:
      commands:
        - npm install -g pnpm
    build:
      commands:
        - pnpm install --frozen-lockfile --store-dir .npm --prefer-offline
        - pnpm ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
frontend:
  phases:
    preBuild:
      commands:
        - npm install -g pnpm
        - pnpm install
    build:
      commands:
        - pnpm run build
# 以下省略...

amplify.ymlはAmplify Hostingで継続的デプロイを行うための設定を記述するファイルです。今回は、pnpmでデプロイするための設定に変更しています。

準備が完了したので、いざ実装を開始します!

実装前にインフラの説明

通常Amplify Gen2では、デフォルトでAppSync(GraphQL)のデータソースにDynamoDBを利用できるのですが、今回はCDKの学習も兼ねているので、以下のようなインフラでデータソースを扱いたいと思います(普通にDynamoDBを利用した方が圧倒的に簡単です)

  • GraphQLのデータソースとしてLambda関数を利用
  • Lambda関数からデータ保存/参照用にElastiCache(Redis)を利用
  • Lambda関数とRedisを同じVPC上に配置してアクセス可能にする
  • フロントではAppSync(GraphQL)経由でLambdaにアクセスしてデータのCURDを実行

スキーマ定義:Amplify Data

今回はチャットアプリなので、メッセージデータのスキーマ定義をします。
スキーマ定義は./amplify/data/resource.tsファイルに記述していきます。
通常DynamoDBをデータソースとする場合は、以下のようにスキーマを作成してdefineDataに設定します。

./amplify/data/resource.ts
const schema = a.schema({
  Message: a
    .model({
      id: a.integer().required(),
      message: a.string().required(),
    })
    // データアクセスをApiキーで認可する
    .authorization((allow) => [allow.publicApiKey()]),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    // データアクセスにApiキーによる認証を利用する
    defaultAuthorizationMode: "apiKey",
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});

これだけで、DynamoDBにデータベースが作られ、定義したスキーマを利用することで、フロントから型安全にデータアクセスすることが可能になります。つまり、この時点でデータ定義とフロントで利用するApiの定義が完了しているんです!(簡単すぎる...)

ただ、今回はデータソースにLambdaを利用したいので、Lambda関数を作成していきます。

Lambda関数実装

今回は、以下の処理を行うLambda関数を作りたいと思います。

  • メッセージの作成
  • メッセージの削除
  • メッセージ一覧取得

Lambda関数では、Redisへのアクセスを行うことになるので、複数の関数が存在すると面倒なので、今回はひとつのLabmda関数で複数の処理を実行できるようにします。つまり、ひとつのLambda関数にアクセスが来たら、値の内容によって、それぞれ操作別の関数に処理を振り分けるようにするということです。

1. Redisへのアクセスをする処理

https://github.com/takemo101/amplify-simple-message/blob/b3c813b06a523ef4a9659514fb11a89fb2fa7577/amplify/functions/message/handlers/repository.ts#L1-L51

2. それぞれの処理を行う関数

メッセージ作成
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/functions/message/handlers/createMessage.ts#L1-L15
メッセージ削除
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/functions/message/handlers/removeMessage.ts#L1-L15
メッセージ一覧取得
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/functions/message/handlers/getMessages.ts#L1-L12

各関数の型は、のちに作成するスキーマ定義から取得することができるので、スキーマに従った関数の定義が可能です!

3. 処理振り分けをするLambda関数

Lambda関数の引数から渡ってきた各関数名と同じ名前のフィールド名(のちに記述するスキーマのプロパティ名)によって処理を振り分ける実装にしています(この方法で振り分けるのはあまり良くないかも?)
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/functions/message/handler.ts#L1-L52

実行すると何故かEventオブジェクト型と若干違うデータが引数に渡されてしまっているので、無理やり型アサーションをしています。

4. Lambda関数のリソース設定

作成した関数をLambda関数リソースとして定義するにはdefineFunctionを使います。
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/functions/message/resource.ts#L1-L6
これだけで、同じディレクトリに定義した./amplify/functions/message/handler.tsをLambda関数リソースとして定義できます。

Lambda関数をデータソースとしたスキーマ定義

Lambda関数が定義できたので、この関数をデータソースとしたスキーマ定義をしていきます。
基本的には「このデータはこのデータソースを使ってこの型のデータを返します」のような設定をする感じです。
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/amplify/data/resource.ts#L1-L65

スキーマ定義にサブスクリプションというものが出てくるのですが、あまり理解していないので説明を省かせてください(すまん!)

AWSのリソース定義:CDK + Amplify Backend

データの準備ができたので、次にCDKを使ったAWSのリソースを定義していきます。
以下のようなリソースを定義したいと考えています。

この辺のインフラ設定はよく分からなかったので、AIに教えてもらいながら設定しました。

1. VPC

Lambda関数とRedisのためのネットワークを提供します。
プライベートなネットワーク環境を構築します。

2. サブネット

RedisクラスターとLambda関数を配置する場所を提供します。
プライベートサブネットのためインターネットへの直接アクセスは不可です。

3. セキュリティグループ

RedisクラスターとLambda関数のアクセスを制御するセキュリティーグループです。

4. Redisクラスター

データをキャッシュするためのインメモリデータストアです。今回はデータの永続化にも使用してみます(本来はキャッシング用途が主です)

5. Lambda関数

Lambda関数は既に作成済みなのですが、このリソースにRedisにアクセスするための情報を環境変数として設定したり、RedisへのアクセスできるようにVPCに置くための設定をします。

Sandbox

まずは、データのリソース定義をしたものを./amplify/backend.tsに設定します。

./amplify/backend.ts
import { data } from './data/resource.js';

const backend = defineBackend({
  data,
});

defineDatadefineFunctionでリソース定義したものをdefineBackendに設定することで、細かいインフラの設定しなくてもAmplifyが良い感じにリソースを作成してくれます。
どのようなリソースが構築されるかを確認したい場合は、以下コマンドを実行してAmplifyのsandboxでデプロイすることができます(デプロイに数分かかります)

# <your profile> の部分は自身で設定したAWSのprofile名を設定してください
pnpm ampx sandbox --profile=<your profile>

もしも、リソースの設定に不備などあれば、デプロイ時にロールバックされるので、安心してデプロイできます!
sandboxを削除したい場合は、以下コマンドを実行してください。

pnpm ampx sandbox delete --profile=<your profile>

CDKでインフラ構築

認証やデータなどはAmplify標準の機能で簡単に実装できるのですが、それ以外のインフラの構築はCDKを使う必要があります...初心者の私には、これが非常に難しかった部分です(何度も設定ミスでデプロイに失敗しました)

基本的には、backend.createStackで作成したスタック(CloudFormationスタック)に対して色々なリソースを定義していくことで、Amplifyのアプリのリソースとして定義できるみたいな感じです。

https://github.com/takemo101/amplify-simple-message/blob/b3c813b06a523ef4a9659514fb11a89fb2fa7577/amplify/backend.ts#L1-L132

色々なリソースが簡単に定義可能

今まで紹介してきた、defineDataによるデータベースやdefineFunctionによるLambda関数の定義以外にも、Amplify Gen2では色々なリソースを簡単に定義できます。

Cognito認証

defineAuthで簡単にCognitoのEメールログインを利用することが可能です。
GognitoではGoogleなどのソーシャルログインにも対応しているのですが、それもdefineAuthで設定できます。

./amplify/auth/resource.ts
import { defineAuth } from "@aws-amplify/backend"

export const auth = defineAuth({
  loginWith: {
    email: true,
  },
})

S3

defineStorageで簡単にS3バケットを作成できます。
S3では、バケットのアクセス制御やストレージイベント監視なども設定できるのですが、それもdefineStorageで設定できます。

./amplify/storage/resource.ts
mport { defineStorage } from '@aws-amplify/backend';

export const storage = defineStorage({
  name: 'amplifyStorage'
});

これらのリソース設定は、データリソース定義と同じようにdefineBackendに追加することで、デプロイ時にリソースが構築されます。
あと、今回は触れませんが、amplify-uiパッケージを利用することで、これらのリソースとフロントの連携が簡単に実装できます。

フロント実装:Next.js

フロントエンドの実装について説明します。
インフラ構築だけでヘトヘトですが、まだフロントエンドの実装が残ってます。

画面説明

以下画面要件です。

  • 「メッセージ」作成ボタンを押下して、表示されたダイアログにメッセージを入力すると一覧にメッセージが追加される。
  • 一覧の各メッセージを押下するとメッセージが削除される。
  • 同時に複数画面を立ち上げて操作しても、表示内容が同期する。
    • メッセージを作成すると、他の画面ではメッセージが一覧に追加される
    • メッセージを削除すると、他の画面ではメッセージが一覧から削除される

コンポーネント実装

まずは、メッセージの作成と削除をするためのMessageSectionクライアントコンポーネントを作成します。

デプロイをするとamplify_outputs.jsonという設定ファイルが作成されるので、Amplify.configureに設定することで、Amplifyのbackendで作成したデータなどがフロントで利用できるようになります。
そして、generateClientで取得したオブジェクト経由でスキーマ定義したデータに型安全にアクセスができるようになります(素晴らしい仕組み!)
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/src/components/MessagesSection.tsx#L1-L101

次にページコンポーネントを作成します。
基本的に作成したMessageSectionを設置するだけですが、初期データとしてサーバーコンポーネントでメッセージ一覧を取得して、MessageSectionのPropsに渡します。
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/src/app/page.tsx#L1-L35

サーバーコンポーネントでは、generateClientではなく、以下のようにサーバーサイド用のクライアントを取得する必要があります。
https://github.com/takemo101/amplify-simple-message/blob/15f86490de220b573ce93336f820136a40fe7063/src/utils/serverClient.ts#L1-L10

これで、フロントの実装完了したので、sandboxが起動した状態で以下コマンドを実行してみましょう!

pnpm dev

localhost:3000にアクセスすると、シンプルなチャット画面が表示されましたよね?

デプロイ

Amplifyでは、Githubに作ったリポジトリからデプロイが可能になっているので、実際に今回のプロジェクトをAmplifyでデプロイしてみました!

デプロイ方法については、色々な記事があるので、そちらを参照してください。

まとめ

今回は、Amplify Gen2を使って簡単なアプリを作成してみましたが、認証・データモデル作成 + データアクセス・Lambda関数など、インフラ意識せずスキーマなどを定義するだけで簡単に利用可能なところに感動しました。

ただ、Amplify Gen2でdefineXXXのような関数が用意されていない場合、CDKを使ったAWSのサービスやインフラの知識が必要とされるリソース定義をする必要があり、インフラが苦手な私には難しいと感じていて 「これを使いこなしている弊社のフロントエンジニア優秀すぎだろ!?」 と尊敬の念が絶えませんでした。

株式会社ソニックムーブ

Discussion