Amazon Web Services ブログ

Amplify Functions: TypeScript を用いた AWS Lambda 上のサーバーレス関数の作成

AWS Amplify は、Gen 2 における Function の一般提供を発表できることを喜ばしく思います。Amplify Functions は TypeScript を使って定義、作成、使用されます。これらは、カスタムクエリやミューテーションのハンドラーであっても、認証リソースのトリガーであっても構いません。Amplify Functions の実態は、 AWS Lambda によって提供されていますが、Amplify を使えばアプリと同じコードベースや言語でサーバーレス関数をデプロイできます。関数はさまざまなユースケースで使用されますが、多くの場合、リソースの動作を拡張または変更してアプリ向けのカスタマイズされたユーザーエクスペリエンスを構築します。

Amplify Gen 2 では、リソースは resource ファイル (つまり resource.ts) で定義されていますが、Function も対応する handler ファイル (つまり handler.ts) で作成されます。Function を使用すると、ファイルを保存するたびにソースコードをホットスワップすることで、素早く反復処理を行えます。TypeScript ベースのリソース定義、TypeScript ベースの handler から始めると、リソースまたは handler ファイルを保存するたびに、esbuild を使ってソースコードがバンドルされ、数秒で再デプロイされます。
Amplify Functions では、各 Function 用に tsconfig.json やビルドプロセスを書く必要はありません。
必要最小限のリソース定義とハンドラーを作成し、デプロイするだけです。

Functions 最大の利点は、ほとんどあらゆるリソースで使えることです! Amplify Functions で最も頻繁に使われるユースケースの一例は次のとおりです。

  1. Amplify でネイティブサポートされていないリソース (例えば AI/ML 向けの Amazon Bedrock) に接続する
  2. ユーザー情報をユーザープロファイルデータレコードにリンクするなど、Amplify の既定の認証フローを変更する
  3. ストレージバケットにアイテムがアップロードされた際にイベントを実行する (例えば画像のリサイズ)
  4. データリソースに対するカスタムクエリやミューテーションのためのリゾルバーを作成する

ここでは、Amplify アプリ内の カスタム AWS リソースとして Amazon Bedrock と対話する関数を構築し、カーソルルームに保存された画像に基づいて俳句を生成します。
この関数は、カスタムクエリのハンドラーとしてデータリソースにアタッチされます。

TypeScript を用いた関数リソースの定義

Amplify Functions を使い始めるには、リソース定義が必要です。

// amplify/functions/generateHaiku/resource.ts 
import { defineFunction } from "@aws-amplify/backend"

export const generateHaiku = defineFunction({
  name: "generateHaiku",
})

そして対応するハンドラー (handler.ts) ファイルは次のようになります:

// amplify/functions/generateHaiku/handler.ts 
export const handler = async () => {}

個人用のクラウドサンドボックスが動作していれば、ハンドラーファイルを保存するとすぐに、最初の関数のデプロイが開始され、数秒以内にデプロイが完了します。

Amplify リソースとの関数の利用

関数がストレージリソースから画像を 読み取る ためには、アクセス権を付与する必要があります。Amplify リソースへのアクセス権は共通の言語で付与され、Amplify は IAM の用語をストレージの read や Auth の addUserToGroup のようなリソースに適した言葉に単純化します。関数のリソース定義を使って、ストレージリソースの room/ パスへのアクセス権を許可しましょう。

import { defineStorage } from "@aws-amplify/backend";
import { generateHaiku } from "../functions/generateHaiku/resource";

export const storage = defineStorage({
  name: 'gen2-multi-cursor-demo-app',
  access: allow => ({
    'room/*': [
      allow.authenticated.to(['get', 'write', 'delete']),
      allow.guest.to(['get', 'write', 'delete']),
      // grant the "generateHaiku" function "read" access
      allow.resource(generateHaiku).to(['read'])
    ]
  })
});

カスタムクエリのリゾルバーとしても Function を利用できます。前に作成したリソース定義を使って、カスタムクエリを作成します。

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

const schema = a.schema({
  // ...
  generateHaiku: a.mutation()
    // specify a "roomId" argument
    .arguments({ roomId: a.string() })
    // we'll return the Haiku as a string
    .returns(a.ref('Haiku'))
    // authorize using an API key
    .authorization(allow => [allow.authenticated()])
    // specify the "generateHaiku" function we just created
    .handler(a.handler.function(generateHaiku))
    
}).authorization((allow) => [allow.authenticated()]);

// ...

これにより、生成された Data クライアントを使用して、Function を型安全な方法で呼び出すこともできます。クエリが client.queries.generateHaiku() で呼び出されると、Function が実行され、Storage リソースから読み取れるようになります。

任意の AWS サービスでの関数の利用

Amplify は AWS Cloud Development Kit (CDK) の上に構築されているため、Amplify が一級のサポートを提供していない AWS サービスとやり取りする必要がある場合は、CDK を使用して Amplify が生成したリソースを拡張または変更できます。
たとえば、Amplify リソースからデータを使用して別の AWS サービス、Amazon Bedrock と通信するための関数を構築します。

// amplify/backend.ts
import { Stack } from "aws-cdk-lib/core"
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"
import { defineBackend } from "@aws-amplify/backend"
import { auth } from "./auth/resource"
import { data } from "./data/resource"
import { storage } from "./storage/resource"
import { AMAZON_BEDROCK_MODEL_ID, generateHaiku } from "./functions/generateHaiku/resource"

/**
 * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
 */
const backend = defineBackend({
  auth,
  data,
  storage,
  generateHaiku,
})

// ...
// access the "generateHaiku" function from your defined backend
const generateHaikuFunction = backend.generateHaiku.resources.lambda

// use built-in CDK construct methods to extend the Function's role
generateHaikuFunction.addToRolePolicy(
  new PolicyStatement({
    effect: Effect.ALLOW,
    actions: ["bedrock:InvokeModel"],
    resources: [
      `arn:aws:bedrock:${
        Stack.of(generateHaikuFunction).region
      }::foundation-model/${AMAZON_BEDROCK_MODEL_ID}`,
    ],
  })
)

TypeScript サポートの拡張

Amplify Functions の定義方法と他のリソースへの接続方法を見てきたところで、次は Amplify が TypeScript を使った Functions の作成体験をどのように向上させているかを見ていきましょう。

環境変数への型指定

Amplify は環境変数について、型の絞り込まれた参照を生成します。環境変数が明示的に宣言されている場合は、関数で生成される env 参照で利用可能になります。例えば、使用する Amazon Bedrock モデル (モデルの詳細はAWS の Amazon Bedrock ドキュメントを参照) を指定する場合は以下のようになります。

// amplify/functions/generateHaiku/resource.ts 
import { defineFunction } from "@aws-amplify/backend";

export const AMAZON_BEDROCK_MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0";

export const generateHaiku = defineFunction({
  name: "generateHaiku",
  environment: {
    AMAZON_BEDROCK_MODEL_ID,
  },
});

この環境変数は、environment のキーを使って env で利用できるようになります。

// amplify/functions/generateHaiku/handler.ts 
import { env } from "$amplify/env/generateHaiku"

console.log(env.AMAZON_BEDROCK_MODEL_ID)

または、ストレージなどの他のリソースへのアクセスを指定した場合は、Amplify がそのリソースのメタデータ (Amazon S3 バケット名など) への参照を作成します。

// amplify/functions/generateHaiku/handler.ts 
import { env } from "$amplify/env/generateHaiku"

console.log(env.GEN_2_MULTI_CURSOR_DEMO_APP_BUCKET_NAME)

型付きハンドラ関数

関数をカスタムクエリやミューテーションのリゾルバーとして割り当てる場合、Amplify は Function のハンドラー用の型を特別に提供します。

// amplify/functions/generateHaiku/handler.ts 
import { env } from "$amplify/env/generateHaiku"
import { type Schema } from "../../data/resource"

// use the prebuilt handler type from your Schema 
type Handler = Schema["generateHaiku"]["functionHandler"] 

export const handler: Handler = async (event) => {
  // arguments are typed based on the query definition 
  const { roomId } = event.arguments 

  // to satisfy the handler's "returnType" requirement, return the Haiku 
  return {
    content: "",
    roomId,
  }
}

事前設定済みの TypeScript ビルド

create-amplify で Amplify アプリを作成すると、Amplify は最新の設定を含む TypeScript プロジェクト設定ファイルをスキャフォールディングし、esbuild を使用して Functions をバンドルします。
ファンクションのエントリポイントごとに個別の設定を心配する必要はありません。また、ビルドスクリプトも不要です。
esbuild と最新の TypeScript プロジェクト設定を使用することで、Amplify は他のモジュールからの TypeScript ファイルのインポートを可能にします。
モノレポの環境では、これによりビジネスロジックをモジューラー化し、パッケージの TypeScript を JavaScript にコンパイルする必要がなくなるため、Functions 間で再利用できます。

関数内のシークレット

Amplify Funcsions は、Lambda 関数と他のAWSサービスに依存した環境変数を定義する際の複雑性と、AWS SDKが実行時にシークレット値を解決するためのお決まりの記述を不要にします。
シークレットは、個人の Cloud 環境インスタンスで使用する場合は CLI で作成されるか、ブランチデプロイの場合は Amplify コンソールで作成されます

Amplify Sandbox では、CLI を使用してシークレットが設定されます。

npx ampx sandbox secret set MY_SECRET 

Secrets は次に、secret() を使って関数の environment にバインドできます。

// amplify/functions/generateHaiku/resource.ts 
import { defineFunction, secret } from "@aws-amplify/backend"

export const generateHaiku = defineFunction({
  name: "generateHaiku",
  environment: {
    MY_SECRET: secret("MY_SECRET")
  },
})

そして環境変数と同様に利用できます。

// amplify/functions/generateHaiku/handler.ts 
import { env } from "$amplify/env/generateHaiku"

console.log(env.MY_SECRET)

Get Started Today!

これまでに以下のことを説明しました。

  • Amplify Functions の定義方法
  • カスタムクエリやミューテーションのリゾルバとして Amplify Functions を使う方法
  • Amplify Functions に他の Amplify リソースへのアクセスを許可する方法
  • Amplify Functions に任意の AWS サービスへのアクセスを許可する方法
  • Amplify Functions での環境変数とシークレットの使用方法
  • Amplify Functions のハンドラの型付けや、その他の TypeScript の機能の使い方

これらの機能が実際の設定でどのように組み合わされているかを確認するには、day-3 ブランチのサンプルアプリを参照してください。
今すぐ Amplify Functions を使い始めるには、npm create amplify@latest を実行し、Amplify Functions のドキュメントを参照して詳細を確認してください!

本記事は、Amplify Functions: Create serverless functions using TypeScript, powered by AWS Lambda を翻訳したものです。翻訳は Solutions Architect の 吉村 が担当しました。