AWSサーバーレス入門 - Auroraを使ったSQL

要約

このシリーズでは、AWSのサーバーレスの基本について説明し、自分自身でサーバーレスアプリケーションを構築できるようにします。前回の記事で、Lambda関数をトリガーするイベントをバッファリングするためにSQSを使用する方法を学びました。今日は、Aurora Serverlessを使ったSQLストレージについて取り組みます!

今日は何をしますか?

  • Aurora Serverless SQLデータベースを作成する
  • データベースと対話するLambda関数を作成する
  • ボーナス: 超シンプルなマイグレーションシステムを構築する

⬇️ サーバーレスに関するコンテンツを定期的に投稿していますので、もっと知りたい方は ⬇️

私のTwitterをフォローしてください 🚀

お知らせ: 🛡 sls-mentor 🛡というライブラリも制作しています。30個のサーバーレスベストプラクティスを自動でチェックするもので、あらゆるフレームワークのAWSサーバーレスプロジェクトで動作します。無料でオープンソースなので、是非チェックしてみてください!

GitHubでsls-mentorを見る ⭐️

SQLサーバーレスストレージ: どうやって可能なの?

わかっています、SQLエンジンは基本的にサーバーレス機能で知られているわけではありません。なぜなら本質的にサーバーが必要だからです。AWSも例外ではありません。彼らのSQLサービスであるAWS RDSは管理されていますが、サーバーのプロビジョニングが必要で、固定費がかかります。

でも、まだ帰らないでください。良いニュースがあります!AWSはAurora Serverlessをリリースしました。これはMYSQL / Postgres互換のSQLエンジンで、自動でスケールし完全に管理されています。使用しない時は自動的に一時停止することもできます:一部の人々(全員ではない)はサーバーレスの定義に相応しいと言うでしょう!

AWS CDKでAurora Serverlessクラスターを準備する

この記事では、usersテーブルを含むSQLデータベースを作成します。ユーザーにはid、firstName、lastNameがあります。次に2つのLambda関数を作成します。一つは新しいユーザーをデータベースに作成するAddUser関数。もう一つはデータベースから全ユーザーを取得するGetUsers関数です。両方の関数はAPI Gatewayを通じて公開されます。

アーキテクチャは以下のようになります:

基本スキーマ

いつものように、この記事ではAWS CDKを使用します。もしCDKに慣れていなければ、スタートするために私のシリーズの前の記事を読むことをお勧めします。新しいCDKプロジェクトを作成した後、新しいオーロラクラスターをプロビジョニングします。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import path from 'path';

export class LearnServerlessStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const dbSecret = new cdk.aws_rds.DatabaseSecret(this, 'AuroraSecret', {
      username: 'admin',
    });

    const cluster = new cdk.aws_rds.ServerlessCluster(this, 'AuroraCluster', {
      engine: cdk.aws_rds.DatabaseClusterEngine.AURORA_MYSQL,
      credentials: cdk.aws_rds.Credentials.fromSecret(dbSecret),
      clusterIdentifier: 'my-aurora-cluster',
      defaultDatabaseName: 'my_database',
      enableDataApi: true,
      scaling: {
        autoPause: cdk.Duration.minutes(10),
        minCapacity: 2,
        maxCapacity: 16,
      },
    });
  }
}

このコードスニペットでは主に2つのことを行っています:

  • データベースのクレデンシャルを格納するためのシークレットを作成します。これは後でLambda関数からデータベースに接続する際に役立ちます。私はパスワードを指定しませんでしたが、それはAWSによって自動的に生成されます(コードに格納したくないでしょう!!!)。

  • ServerlessClusterを作成します。これはAurora Serverlessのコンストラクトです。

    • エンジン(MYSQL、postgresも利用可能)を指定します
    • さきほど作成したシークレットを使用してクレデンシャルを指定します
    • デフォルトのデータベース名を指定します:作成時にクラスターにデータベースを作成します
    • データAPIを有効にします。これはAurora Serverlessの重要な機能で、HTTPリクエストを使用してデータベースと対話でき、Lambda関数の開発を大幅に簡素化します。
    • 最後に、オートスケーリングを設定します。最小と最大の容量はACUsで測定されます(詳しくはこちら)。負荷に応じて自動的にスケールアップおよびダウンします。オートポーズは、クラスターが一時停止するまでの非アクティブ時間であり、お金を節約できます(一時停止中はストレージのみが請求されます)。

また、多数のネットワーキング、高度なセキュリティ機能などを追加することもできましたが、Aurora Serverlessの素晴らしさは、最小限の設定でそのまま動作することです!

Lambda関数を使用してAurora Serverlessと対話する

さて、新しいデータベースと対話する2つのLambda関数を作成しましょう。最初の関数はデータベースに新しいユーザーを作成し、2つめの関数はデータベースから全ユーザーを取得します。まずはAWS CDKを使用してLambda関数をプロビジョニングします。

// ...前のコード、まだコンストラクタの中

const api = new cdk.aws_apigateway.RestApi(this, 'api', {});

const addUser = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'addUser', {
  entry: path.join(__dirname, 'addUser', 'handler.ts'),
  handler: 'handler',
  environment: {
    CLUSTER_ARN: cluster.clusterArn,
    SECRET_ARN: cluster.secret?.secretArn ?? '',
  },
  timeout: cdk.Duration.seconds(30),
});

cluster.grantDataApiAccess(addUser);

const getUsers = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'getUsers', {
  entry: path.join(__dirname, 'getUsers', 'handler.ts'),
  handler: 'handler',
  environment: {
    CLUSTER_ARN: cluster.clusterArn,
    SECRET_ARN: cluster.secret?.secretArn ?? '',
  },
  timeout: cdk.Duration.seconds(30),
});

cluster.grantDataApiAccess(getUsers);

api.root.addResource('add-user').addMethod('POST', new cdk.aws_apigateway.LambdaIntegration(addUser));
api.root.addResource('get-users').addMethod('GET', new cdk.aws_apigateway.LambdaIntegration(getUsers));

ここでは、2つのLambda関数を作成し、それらをAPIに接続しています。AddUsersはPOSTリクエストによって、GetUsersはGETリクエストによってトリガーされます。**重要:**また、Lambda関数にクラスターのデータAPIへのアクセスを許可し、環境変数としてクラスターARNとシークレットARNを渡しています。これらをLambda関数のコードで使用できます。データAPIがデータベースが一時停止しているときに応答するのに時間がかかる可能性があるため、30秒のタイムアウトを設定しました。

さて、興味深い部分:Lambda関数のコードです。addUser関数から始めましょう。

import { ExecuteStatementCommand, RDSDataClient } from '@aws-sdk/client-rds-data';
import { v4 as uuid } from 'uuid';

const rdsDataClient = new RDSDataClient({});

export const handler = async ({ body }: { body: string }): Promise<{ statusCode: number; body: string }> => {
  const secretArn = process.env.SECRET_ARN;
  const resourceArn = process.env.CLUSTER_ARN;

  if (secretArn === undefined || resourceArn === undefined) {
    throw new Error('環境変数がありません');
  }

  const { firstName, lastName } = JSON.parse(body) as { firstName?: string; lastName?: string };

  if (firstName === undefined || lastName === undefined) {
    return {
      statusCode: 400,
      body: 'firstNameまたはlastNameがありません',
    };
  }

  const userId = uuid();

  await rdsDataClient.send(
    new ExecuteStatementCommand({
      secretArn,
      resourceArn,
      database: 'my_database',
      sql: 'CREATE TABLE IF NOT EXISTS users (id VARCHAR(255) NOT NULL PRIMARY KEY, firstName VARCHAR(255) NOT NULL, lastName VARCHAR(255) NOT NULL); INSERT INTO users (id, firstName, lastName) VALUES (:id, :firstName, :lastName);',
      parameters: [
        { name: 'id', value: { stringValue: userId } },
        { name: 'firstName', value: { stringValue: firstName } },
        { name: 'lastName', value: { stringValue: lastName } },
      ],
    }),
  );

  return {
    statusCode: 200,
    body: JSON.stringify({
      userId,
    }),
  };
};

このコードスニペットでは基本的に3つのことを行っています:

  • 先ほど渡した環境変数を取得し、定義されていることを確認します。
  • リクエストのボディを解析し、firstNameとlastNameを抽出します。
  • データベース my_databaseに対して2つのSQLコマンドを実行します:存在しない場合はusersテーブルを作成し、その後テーブルに新しいユーザーを挿入します。

⚠️覚えておいてください:クラスターでデータAPIを有効にしたので、シンプルなrdsDataClientコマンドを実行できます。そうでなければ、もっと複雑になります。

さて、getUsers関数のコードを見てみましょう。

import { ExecuteStatementCommand, RDSDataClient } from '@aws-sdk/client-rds-data';

const rdsDataClient = new RDSDataClient({});

export const handler = async (): Promise<{ statusCode: number; body: string }> => {
  const secretArn = process.env.SECRET_ARN;
  const resourceArn = process.env.CLUSTER_ARN;

  if (secretArn === undefined || resourceArn === undefined) {
    throw new Error('環境変数がありません');
  }

  const { records } = await rdsDataClient.send(
    new ExecuteStatementCommand({
      secretArn,
      resourceArn,
      database: 'my_database',
      sql: 'SELECT * FROM users;',
    }),
  );

  const users =
    records?.map(([{ stringValue: id }, { stringValue: firstName }, { stringValue: lastName }]) => ({
      id,
      firstName,
      lastName,
    })) ?? [];

  return {
    statusCode: 200,
    body: JSON.stringify(users, null, 2),
  };
};

このハンドラでは基本的に3つのことを行っています:

  • 先ほど渡した環境変数を取得し、定義されていることを確認します。
  • RDSデータAPIを使用して、データベース名 my_databaseusersテーブルに対してSELECT文を実行します。
  • クエリの結果を整形し、JSONレスポンスとして返します。

これで終わりです!APIをデプロイしてテストする時間です。

npm run cdk deploy

私はPOSTリクエストを1つだけ簡単に実行してユーザーを追加します:ユーザーの追加

そして、すべてのユーザーを取得するためのGETリクエストを実行します:[ユーザーの取得](https://res.cloudinary.com/practicaldev/image/fetch/s--0KM2Rcj7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/PChol22/kumo-articles/master

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/slsbytheodo/learn-serverless-on-aws-step-by-step-sql-with-aurora-5hn1