前回の記事ではAWS Lambdaがステートレス基盤であることを踏まえ、予測を超えたRDBMSへの新規コネクションリクエストが発生してしまうケースと、Lambdaと連携してコネクションプーリングを行うRDS Proxyというサービスについて紹介しました。
https://serverless.co.jp/blog/ee_1qfx155c6/
今日はObject-Relational Mapping(ORM)を使った接続をやっていきます。
ORMとは
Object-Relational Mapping(ORM)とは、オブジェクト指向プログラミングとリレーショナルデータベース(RDB)との間でデータの変換を行う技術です。開発者がデータベースのデータをSQLで直接操作するのではなく、オブジェクトを通じてデータを操作できるようにする仕組みです。このため開発中の言語でそのままデータ操作が行えることから、データベースとのやり取りをより直感的に、かつ効率的に行うことができます。
ORMを実現するツールはいろいろありますが、今日はその中でもメジャーなPrismaを触っていきます。Prismaは特にTypescriptとの連携性が高いといわれており、いわゆる強力な型推論をそのままデータ操作に持ち込めるのが魅力の一つです。
https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma

Prisma schemaという設定ファイルがDBのスキーマを認識することで、Prisma clientがTypeScript/JavaScriptからSQLを発行せずにデータ操作を行える、という関係性です。つまりPrisma clientは裏側でSQLをデータベースに対して発行しています。
このためSQLインジェクション攻撃などのような外部から不正なデータ操作を目的としてSQLを投げ込む攻撃に対するRDBMSの体制を向上させるといった効果も期待できます。
またPrisma clientは前回ご紹介したRDS Proxyのようなコネクションプーリングの機能も提供してくれています。しかしながらLambdaの場合、実行環境のスピンアップ時に初期化されたPrisma clientは処理完了後一定時間を経てリソースが解放されてしまいますので、FaaS環境の場合その機能は期待できません。このためPrismaの接続先をRDS Proxyにする、という構成も可能です。
さっそくやってみる
こんかいはJavaScriptでRDS Proxyを用いない最もシンプルな手順を示します。
0.MySQLの準備
なんでもよいのでLambdaから通信可能なMySQLデータベースを起動しておきます。その後接続を行い以下のSQLを実行しておいてください。
CREATE TABLE User ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255) );
データを3行ほど書き込んでおきます。
INSERT INTO User (email, name) VALUES ('user1@example.com', 'Alice Smith'), ('user2@example.com', 'Bob Johnson'), ('user3@example.com', 'Charlie Brown');
1.プロジェクトの作成
ではまずプロジェクトを作成します。
mkdir prisma-lambda-rds
cd prisma-lambda-rds
npm init -y
2.PrismaとMySQLの依存パッケージのインストール
つぎにnpmを用いて必要なライブラリをインストールします。この手順ではMySQLを使っていますが、もちろんそれ以外でのDBエンジンでも動作します。
https://www.prisma.io/docs/orm/overview/databases (サポートしているDBエンジン一覧)
npm install prisma @prisma/client mysql2
3.Prisma の初期化
prisma自体をまずは初期化します。これにより前述したprisma/schema.prismaとうファイルが作成されます。
npx prisma init
4.schema.prismaの設定
以下の内容をコピペします。
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-3.0.x"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
env("DATABASE_URL")
は後ほどLambdaの環境変数でMySQLへの接続文字列を設定します。
以下の部分がPrismaの肝です。先ほど事前準備で作成しておいたテーブルをモデル という形で認識させます。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
5. Prisma client の生成
設定ファイル(Prisma schema)ができたらPrisma clientを生成します。
npx prisma generate
実行すると`.prisma\client\index.js`が生成されており、その中にschemaで定義されているモデルなどが含まれていますが、このjs自体は特に気にしなくても(修正などの作業を行わなくても)大丈夫です。
Prisma schemaで指定した以下のモジュールも自動でインストールされます。
binaryTargets = ["native", "rhel-openssl-3.0.x"]
ちなみにこれは、`Red Hat Enterprise Linux (RHEL)上で、OpenSSL 3.0.xを使用するバージョンに対応したバイナリを生成することを意味します。これを指定することで、RHELを使用しているシステムでOpenSSL 3.0.xに適合するバージョンのPrisma Clientをビルドします。LambdaはRHELではなさそうですが、とりあえずこれで動作します。
6. Lambda関数の作成
index.jsで以下のファイルを作成します。
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
exports.handler = async (event) => {
try {
const users = await prisma.user.findMany();
return {
statusCode: 200,
body: JSON.stringify(users),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
} finally {
await prisma.$disconnect(); // Prismaの接続を必ず切断
}
};
7.Lambda 関数のDeployとテスト
ではLambda関数をDeployします。まず作業フォルダをZIPで固めます。
ZIPをLambdaに直接アップロード可能な10MB制限は超えているはずなのでS3バケットにアップロードしたのちLambdaにインポートします。

テスト前に環境変数を設定します。

キー:DATABASE_URL
値:mysql://<id>:<password> @<DB接続エンドポイント>:<port番号>/my_database
<xxxx>の値は皆さんの環境に置き換えてください。
8.テスト実行
では最後にテストです。このサンプルスクリプトは特にInputをとっていませんので、イベントは何でもよいです。

テーブルの中身が出てくれば成功です。
これは以下のスクリプトの実行結果になります。
exports.handler = async (event) => {
try {
const users = await prisma.user.findMany();
return {
statusCode: 200,
body: JSON.stringify(users),
}
これは SELECT * FROM "user";
を意味しています。例えばInsertであれば以下です。
const newUser = await prisma.user.create({
data: {
name: name,
email: email,
},
});
書くSQLクエリに呼応するPrisma関数のリファレンスはこちらにあります。