CDKユーザーに伝えたい! Amplify Gen2の拡張性
はじめに
こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!
この記事では、AWS CDK を用いた AWS Amplify Gen2 の拡張性に焦点を当てたいと思います。
目標
Cognitoで認証されたユーザーの画像をS3にUpLoadしたイベントによってLambdaを起動する
そんな構成、よくありますよね。こちらを Amplify Gen2 で簡単に作っていこうと思います。
ちなみに Amplify Gen2 とは、Amplify の次世代開発体験のことです。
(詳しくは拙著をご覧ください)
Gen1 との大きな違いは、リソースのデプロイ方法が CLI から CDK に変更されたことです。
これにより Gen2 では、Amplify で作成したリソースを CDK で拡張することが非常に容易になりました。
また、AWS CDK については、公式ドキュメントをご覧ください。
Amplify Gen2 のセットアップ
先ほどの拙著、もしくは公式 Quickstartに記載がある通り、Sandbox 環境構築まで済ませてしまいます。
# next.jsのセットアップ
$ npm create next-app@14 -- next-amplify-gen2 --typescript --eslint --app --no-src-dir --no-tailwind --import-alias '@/*'
$ cd next-amplify-gen2
# amplify gen2のセットアップ
$ npm create amplify@latest
> ? Where should we create your project? (.) # press enter
# 必要なライブラリのインストール
$ npm install @aws-amplify/ui-react @aws-amplify/ui-react-storage
# amplify sandboxの起動
$ npx ampx sandbox
Next.js の App Router で初期設定を済ませた場合以下のようなファイル構成になると思います。
├── amplify/
│ ├── auth/
│ │ └── resource.ts
│ ├── data/
│ │ └── resource.ts
│ ├── backend.ts
│ └── package.json
│
├── app/
│ ├── favicon.ico
│ ├── layout.tsx
│ └── page.tsx
│
amplify/auth
とamplify/data
は今回出番がないので、デフォルト設定のままです。
amplify/auth/resource.ts
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
loginWith: {
email: true,
},
});
amplify/data/resource.ts
import { a, defineData, type ClientSchema } from '@aws-amplify/backend';
const schema = a.schema({
User: a
.model({
name: a.string(),
imageKey: a.string(),
})
.authorization((allow) => [allow.owner()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
},
});
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
const backend = defineBackend({
auth,
data,
});
app
以下は Login して Amplify の各種機能を呼び出せるようにしておきます。
app/page.tsx
'use client';
import { withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
function App({ signOut }: { signOut: any }) {
return (
<>
<h1>Hello, Amplify 👋</h1>
<button onClick={signOut}>Sign out</button>
</>
);
}
export default withAuthenticator(App);
components/ConfigureAmplify
'use client';
import config from '@/amplify_outputs.json';
import { Amplify } from 'aws-amplify';
Amplify.configure(config, { ssr: true });
export function ConfigureAmplifyClientSide() {
return null;
}
app/layout.tsx
import { ConfigureAmplifyClientSide } from '@/components/ConfigureAmplify';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang='en'>
<body className={inter.className}>
<ConfigureAmplifyClientSide />
{children}
</body>
</html>
);
}
Storage カテゴリの追加
次に Storage カテゴリを追加していきます。
Amplify の Storage カテゴリとは、バックエンドのS3作成 + 認証周りの設定
をよしなにしてくれるものです。
amplify
以下に storage を追加します。
├── amplify/
│ ├── auth/
│ │ └── resource.ts
│ ├── data/
│ │ └── resource.ts
+│ ├── storage/
+│ │ └── resource.ts
│ ├── backend.ts
│ └── package.json
│
├── app/
│ ├── favicon.ico
│ ├── layout.tsx
│ └── page.tsx
│
import { defineStorage } from '@aws-amplify/backend';
export const storage = defineStorage({
name: 'myProjectFiles',
access: (allow) => ({
'public/*': [
allow.guest.to(['read']),
allow.authenticated.to(['read', 'write', 'delete']),
],
'protected/{entity_id}/*': [
allow.authenticated.to(['read']),
allow.entity('identity').to(['read', 'write', 'delete']),
],
'private/{entity_id}/*': [
allow.entity('identity').to(['read', 'write', 'delete']),
],
}),
});
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
+import { storage } from './storage/resource';
const backend = defineBackend({
auth,
data,
+ storage,
});
Amplify Gen2 になって Storage への認証パターンを自分で設定することが可能になりました。今回は、Amplify UI ライブラリを利用したいので、Gen1 と同じパターンにしています。
詳しくは、公式ドキュメントをご覧ください。
これに合わせて、UI 側も画像を UpLoad できるように修正します。
'use client';
import { withAuthenticator } from '@aws-amplify/ui-react';
+import { StorageManager } from '@aws-amplify/ui-react-storage';
import '@aws-amplify/ui-react/styles.css';
function App({ signOut }: { signOut: any }) {
return (
<>
<h1>Hello, Amplify 👋</h1>
<button onClick={signOut}>Sign out</button>
+ <StorageManager
+ acceptedFileTypes={['image/*']}
+ accessLevel='protected'
+ maxFileCount={1}
+ />
</>
);
}
export default withAuthenticator(App);
ここまで修正すると、画像を S3 に UpLoad できるようになっていると思います。
CDK での拡張
次に、先ほど作成したリソースを CDK で拡張していきます。
amplify
以下に custom を追加します。
├── amplify/
│ ├── auth/
│ │ └── resource.ts
+│ ├── custom/
+│ │ └── eventNotifications
+│ │ ├── function.ts
+│ │ └── resource.ts
│ ├── data/
│ │ └── resource.ts
│ ├── storage/
│ │ └── resource.ts
│ ├── backend.ts
│ └── package.json
│
├── app/
│ ├── favicon.ico
│ ├── layout.tsx
│ └── page.tsx
│
eventNotifications/resource.ts
の中身は完全に CDK Construct です。
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { EventType, IBucket } from 'aws-cdk-lib/aws-s3';
import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications';
import { Construct } from 'constructs';
import * as url from 'node:url';
type EventNotificationsProps = {
storage: IBucket;
};
export class EventNotifications extends Construct {
constructor(scope: Construct, id: string, props: EventNotificationsProps) {
super(scope, id);
const { storage } = props;
const func = new NodejsFunction(this, 'function', {
entry: url.fileURLToPath(new URL('function.ts', import.meta.url)),
runtime: Runtime.NODEJS_20_X,
});
storage.addEventNotification(
EventType.OBJECT_CREATED,
new LambdaDestination(func)
);
}
}
backend.ts 側で EventNotifications Construct を設定します。
なんと defineBackend で作成した backend から Stack を作成し、拡張することが可能です!
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
+import { EventNotifications } from './custom/eventNotifications/resource';
import { data } from './data/resource';
import { storage } from './storage/resource';
const backend = defineBackend({
auth,
data,
storage,
});
+const eventNotifications = new EventNotifications(
+ backend.createStack('EventNotifications'),
+ 'EventNotifications',
+ { storage: backend.storage.resources.bucket }
+);
ちなみにeventNotifications/function.ts
は Lambda で実行される関数本体です。
今回は、ただただログをはくだけにしておきます。
import { Context, Handler, S3Event } from 'aws-lambda';
export const handler: Handler = async (event: S3Event, context: Context) => {
console.log({ event, context });
};
sandbox を起動したままであれば、ファイル保存と同時に Lambda が AWS 上にデプロイされると思います。私の場合は、amplify-nextamplifygen2-a-EventNotificationsfuncti-<ランダム英数字>
のような関数名になっていました。
ちゃんと S3 からのイベントも設定されていますね。
それでは、先ほど同様に画像を UpLoad してみます。
そうすると無事に Lambda が起動し、ログが吐かれているのが確認できると思います。
{
event: { Records: [ [Object] ] },
context: {
callbackWaitsForEmptyEventLoop: [Getter/Setter],
succeed: [Function (anonymous)],
fail: [Function (anonymous)],
done: [Function (anonymous)],
functionVersion: '$LATEST',
functionName: 'amplify-nextamplifygen2-a-EventNotificationsfuncti-',
...
}
}
無事に Amplify で作成された S3 を CDK を用いて拡張することができました!
最後に
ここまで読んでいただきありがとうございました。
さらに詳しく知りたい方は、公式ドキュメントに詳細が記載されていますので、そちらも併せてご覧ください。
何だかすごい進化しましたね、AWS Amplify。
まだプレビュー段階なので、まだまだ進化を期待したいところです。
GA されればぜひオトとりっぷでも活用してみたいと思います。
もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!
Discussion