Amazon Web Services ブログ

AWS Amplify の新機能: SQL データベース、OIDC/SAML プロバイダー、AWS CDK との統合

AWS Amplify のローンチウィークの「拡張性の日」へようこそ! AWS AmplifyGen 2 では、ビジネスニーズに合わせてアプリをカスタマイズするための 4 つの新しい機能を提供しています。本日発表する機能は以下の通りです。

  1. 既存のデータソース (PostgreSQL または MySQL データベースを含む) との統合
  2. 任意の OpenID Connect (OIDC) または SAML 認証プロバイダーによる認証
  3. Amplify で生成された AWS サービスのリソース (Amazon Simple Storage Service (S3) バケットや Amazon DynamoDB テーブルなど) のカスタマイズ
  4. 200 を超える AWS サービスをアプリに追加

既存のデータソースとの統合 (PostgreSQL データベースを含む)

Amplify Gen 2 は、既存の PostgreSQL または MySQL データベースに接続するための、優れた統合機能を提供しています。これにより、PostgreSQL や MySQL データベースに対してリアルタイムのサブスクリプションやきめ細かなアクセス制御ができるようになりました。データベースは AWS 外部 (例: Neon) または AWS 内部 (例: Amazon RDS または Amazon Aurora) のどちらにもデプロイできます。今回のデモでは、PostgreSQL データベースのリアルタイム API を構築し、ユーザーがノートの作成とサブスクリプションができるようにします。

Demo of the real-time notes

PostgreSQL や MySQL を使用していませんか? 問題ありません。次のサービスと統合する方法のガイドが提供されています: Amazon DynamoDBAmazon OpenSearchAmazon EventBridgeAmazon PollyAmazon BedrockAmazon RekognitionAmazon Translate、または任意の HTTP エンドポイント。使いたいデータソースがリストにない場合でも? AWS Lambda 関数に接続できるものであれば、Functions 機能を使って何でも統合できます要するに Amplify Gen 2 は、あらゆるものと統合可能なのです

Neon (AWS ではない外部の PostgreSQL データベースサービス) との統合を見ていきましょう。まず、Amplify Gen 2 で React のスターターアプリケーションをセットアップします。

npm create vite@latest gen2-postgres-test

スターターオプションとして「React + TypeScript」を選択します。次に、新しい Amplify Gen 2 プロジェクトを初期化します。

npm create amplify@latest

さらに、後で使用する認証 UI を素早く設定できるように、React UI ライブラリもインストールしてください。

npm install @aws-amplify/ui-react

次に、Neon のウェブサイトにアクセスし、新しいデータベースを作成します。以下の PostgreSQL スキーマを使用できます。

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS postgis;

-- Create a table to events
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  geom GEOMETRY(Point, 4326)
);

-- Create a spatial index on the geometry column
CREATE INDEX idx_events_geom ON events USING GIST (geom);

-- Insert some sample data
INSERT INTO events (name, geom)
VALUES
  ('Event A', ST_SetSRID(ST_MakePoint(-73.985428, 40.748817), 4326)),
  ('Event B', ST_SetSRID(ST_MakePoint(-73.990673, 40.742051), 4326)),
  ('Event C', ST_SetSRID(ST_MakePoint(-73.981226, 40.744681), 4326)),
  ('Event D', ST_SetSRID(ST_MakePoint(-73.987369, 40.739917), 4326)),
  ('Event E', ST_SetSRID(ST_MakePoint(-73.992743, 40.746035), 4326));

-- Create a notes table where we'll add owner-based authorization
CREATE TABLE notes (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    content TEXT,
    owner TEXT
);

Demo showing how to create a DB schema in Neon

次に、データベース接続文字列を取得し、それをシークレットとして Amplify に提供します。

npx ampx sandbox secret set SQL_CONNECTION_STRING

次に、SQL データベーススキーマの TypeScript 表現を生成します。

npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --out amplify/data/schema.sql.ts

Demo of how to set SQL connection string

スキーマを amplify/data/resource.ts ファイルにインポートし、Amplify の Data クライアントを利用してテーブルに対して作成、読み取り、更新、削除の操作を許可する認可ルールを設定できます。

次の例では、Data クライアントで notes テーブルの名前を、より適切に表される名前 Note に 変更し、オーナーに対する認可を追加しています。

import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
import { schema as generatedSqlSchema} from './schema.sql'

const schema = generatedSqlSchema.renameModels(() => [
  ['notes', 'Note'] // Renames the models from "notes" -> "Note"
]).setAuthorization((models) => [
  // Defines owner-based authorization rule and isolates the record
  // based on the record owner. The owner is the user identifier 
  // stored in the column named "owner".
  models.Note.authorization(allow => allow.ownerDefinedIn('owner'))
])

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'userPool',
  },
});

クラウドサンドボックスを起動して、アプリケーションのデータバックエンドのイテレーションを開始します。

npx ampx sandbox

次に、クライアント側で、型指定された Data クライアントを使用して、バックエンドのデータを取得または更新できます。例えば、React コードでは、Amplify のリアルタイムサブスクリプション機能を利用して、observeQuery でデータの変更を監視することができます。src/App.tsx ファイルを次のコードで編集してください。

import { useEffect, useState } from 'react'
import { generateClient } from 'aws-amplify/api'
import { type Schema } from '../amplify/data/resource'
import { Amplify } from 'aws-amplify'
import outputs from '../amplify_outputs.json'
import { withAuthenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'

Amplify.configure(outputs)

const client = generateClient<Schema>();

function App() {
  const [notes, setNotes] = useState<Schema["Note"]["type"][]>([])
  useEffect(() => {
    const sub = client.models.Note.observeQuery().subscribe({
      next: (data) => {
        setNotes([...data.items])
      }
    })
    return () => sub.unsubscribe() 
  }, [])

  return (
    <>
      <h1>Note</h1>
      <ul>
        {notes?.map(note => (
          <li key={note.id} onClick={async () => {
            await client.models.Note.delete({ id: note.id })
          }}>
            <div>{note.content}</div>
          </li>
        ))}
      </ul>
      <button onClick={async () => {
        await client.models.Note.create({
          content: window.prompt("New note?"),
        })
      }}>Create Note</button>
    </>
  )
}

export default withAuthenticator(App)

変更をローカルでテストすると、リアルタイムでメモを作成できるはずです。

npm run dev

以下のデモビデオでは、各ウィンドウで同一のユーザーとしてログインしており、1 つのウィンドウで新しく作成したメモが他のウィンドウに自動的に同期されています。

Demo of the real-time notes

カスタムクエリやミューテーションを追加して、特定の SQL クエリを実行することもできます。例えば、PostGIS などの PostgreSQL 拡張機能を使用して、地理空間クエリを実行できます。

const schema = generatedSqlSchema.renameModels(() => [
  ['notes', 'Note']
]).setAuthorization((models) => [
  models.Note.authorization(allow => allow.ownerDefinedIn('owner'))
]).addToSchema({
  // Adds a new query that uses PostgreSQL's PostGIS extension to
  // convert a geom type to lat and long coordinates
  listEventWithCoord: a.query()
    .returns(a.ref('EventWithCoord').array())
    .authorization(allow => allow.authenticated())
    .handler(a.handler.inlineSql(`
      SELECT id, name, ST_X(geom) AS longitude, ST_Y(geom) AS latitude FROM events;
    `)),
  
  // Defines a custom type that matches the response shape of the query
  EventWithCoord: a.customType({
    id: a.integer(),
    name: a.string(),
    latitude: a.float(),
    longitude: a.float(),
  })
})

この新しいクエリを Data クライアントから呼び出すことができます。例えば、ボタンを追加し、結果のポップアップアラートを作成しましょう。

<button onClick={async () => {
    const { data } = await client.queries.listEventWithCoord()
    window.alert(JSON.stringify(data))
}}>Fetch events</button>

Demo of fetching events

準備ができたら、PostgreSQL データベースに対応した新しい API を Git にコミットして Amplify でホスティングします。ブランチ環境に対して AWS Amplify コンソールで SQL_CONNECTION_STRING シークレットを設定することを忘れないでください。

OpenID Connect または SAML 認証プロバイダーによる認証

Amplify Gen 2 は、任意の OpenID Connect (OIDC) プロバイダーまたは 任意の SAML プロバイダーでユーザー認証を柔軟に行うことができます。つまり、Microsoft EntraID、Auth0、Okta などの一般的な ID プロバイダーや、独自のカスタム OIDC プロバイダーや SAML プロバイダーをアプリと統合できるということです。

OIDC プロバイダーをセットアップするには、amplify/auth/resource.ts ファイルの defineAuth ハンドラーで構成できます。例えば、Microsoft EntraID プロバイダーをセットアップする場合は、次のように行います。

import { defineAuth, secret } from '@aws-amplify/backend';

export const auth = defineAuth({
  loginWith: {
    email: true,
    externalProviders: {
      oidc: [
        {
          name: 'MicrosoftEntraID',
          clientId: secret('MICROSOFT_ENTRA_ID_CLIENT_ID'),
          clientSecret: secret('MICROSOFT_ENTRA_ID_CLIENT_SECRET'),
          issuerUrl: '<your-issuer-url>',
        },
      ],
      logoutUrls: ['http://localhost:3000/', 'https://mywebsite.com'],
      callbackUrls: [
        'http://localhost:3000/profile',
        'https://mywebsite.com/profile',
      ],
    },
  },
});

注: プロダクションブランチの場合は AWS コンソール、クラウドサンドボックスの場合は npx ampx sandbox secret set で、シークレットを必ず設定してください。

フロントエンドでは、signInWithRedirect() 関数を呼び出すだけで、OIDC/SAML 認証ワークフローをトリガーできます。

import { signInWithRedirect } from 'aws-amplify/auth';

await signInWithRedirect({
  provider: {
    custom: 'MicrosoftEntraID'
  }
});

Amplify で生成された AWS サービスリソースのカスタマイズ

お客様から「Amplify を使うのは AWS で裏技を使っているようだ」と言われたことがあります。しかし、時にはその裏技だけでは不十分で、生成されたリソースをカスタマイズして、ユースケースに最適化する必要が出てくることもあるでしょう。私たちはこれを 謎めいたものではなく、魔法のように直感的なもの と考えています

「バケットに保存したファイルを 30 日後に削除する必要がある場合はどうすればよいでしょうか?」

Amplify を使わない場合、30 日ごとに関数をトリガーし、すべてのファイルを辿ってから削除する、複雑な cron ジョブを構築する必要があります。これは維持管理が必要で、さらにはコストもかかる機能です。

Amplify を使う場合、基盤となるサービスの機能を十分に活用することができます。Amplify Storage は Amazon S3 で構築されており、オブジェクトのライフサイクルルールを設定できます。バケットにオプションを設定するだけで済みます。amplify/backend.ts ファイルにて、生成された S3 バケットに対してオブジェクトのライフサイクルルールを設定するだけです。

const backend = defineBackend({
  auth,
  data,
  storage,
});

// Override generated resources
backend.storage.resources.cfnResources.cfnBucket.lifecycleConfiguration = {
  rules: [{
    expirationInDays: 30,
    prefix: 'room/',
    status: 'Enabled'
  }]
}

Amplify の Data, Auth, Storage, Functions など、すべての機能をオーバーライドできるようにサポートしています。

200 を超える AWS サービスをアプリに追加

アプリが成長するにつれ、Amplify の組み込み機能を超えた機能を必要とするようになるのは避けられません。それでも問題ありません。すべてのアプリはその目的を達成するために最適なサービスを利用すべきです。そのため、Amplify アプリに 200 以上の AWS サービスを追加できるようにしました。

Amplify Gen 2 は、AWS Cloud Development Kit (CDK) の上に構築されています。これにより、TypeScript の高い表現力を活かして、クラウド上で信頼性が高く、スケーラブルで、コストパフォーマンスに優れたアプリケーションを開発できます。

「アプリケーションに生成 AI のユースケースのための Amazon Bedrock や、通知のファンアウト機能を追加する必要がある場合はどうすればいいのでしょうか?」

Amplify を使わない場合、アプリケーションのフロントエンドとは別に、バックエンドインフラストラクチャのコードベースを管理する必要があります。つまり、フロントエンドとバックエンドのデプロイを同期するために数週間の DevOps オーバーヘッドがかかり、開発、テスト、本番の各ワークフローをカスタムで構築しなければなりません。

Amplify を使う場合、Amplify アプリにバックエンドインフラストラクチャを簡単に追加できます。Amplify の CI/CD 機能が組み込まれているため、DevOps が手間なく実現できます。新しい環境が必要ですか? Git ブランチを作成するだけです。ローカルでフロントエンドのコードをテストするためのサンドボックス環境が必要ですか? Amplify のクラウドサンドボックスを使用します。

200 以上の AWS サービスからリソースを追加するには、それぞれの CDK コンストラクトをインポートし、amplify/backend.ts ファイルのスタックに追加します。もちろん、Amplify は AWS によって構築され、AWS 上で動作するため、これらのカスタムリソースを Amplify で生成されたリソースと接続することができます

import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { generateHaiku } from './functions/generateHaiku/resource';
import { handleSubscription } from './functions/handleSubscription/resource';
// Import CDK constructs directly into an Amplify Project
import * as iam from 'aws-cdk-lib/aws-iam';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';

const backend = defineBackend({
  auth,
  data,
  generateHaiku,
  handleSubscription
});

// Grant function access to Amazon Bedrock
backend.generateHaiku.resources.lambda.addToRolePolicy(
  new iam.PolicyStatement({
    effect: Effect.ALLOW,
    actions: ["bedrock:InvokeModel"],
    resources: [
      `arn:aws:bedrock:${Stack.of(backend.data).region}::foundation-model/anthropic.claude-3-haiku-20240307-v1:0`,
    ],
  })
)

// Define a new stack for the SNS topic
const snsStack = backend.createStack("SnsStack");

// Create the SNS topic
const topic = new sns.Topic(snsStack, "Topic");

// Use an Amplify Function (defineFunction) as a subscription handler 
topic.addSubscription(new subscriptions.LambdaSubscription(
  backend.handleSubscription.resources.lambda
));

まとめ

これは AWS Amplifiy プロジェクトを拡張する方法の一例にすぎません。私たちは、DevOps ワークフローやデータソースの接続のために、差別化につながらない手作業によるコーディングで時間を浪費することをなくしたいと考えています。インフラストラクチャの設定に悩むのではなく、アプリケーションのユースケースに集中していただきたいのです。まずは、Amplify のクイックスタートチュートリアルをお試しください!

本記事は New in AWS Amplify: Integrate with SQL databases, OIDC/SAML providers, and the AWS CDK を翻訳したものです。翻訳は Solutions Architect の都築が担当しました。