Serverless Operations, inc

>_cd /blog/id_bu27lkhpb

title

FaaS基盤からRDBMS接続における注意点と接続パターン その2 Object-Relational Mapping(ORM) / Prisma

summary

AWS Lambdaに代表されるFaaS基盤は原則ステートレスでありDBへの接続も注意が必要です。この記事では3回連載で接続パターンのベストプラクティスをまとめます。

その1:FaaS基盤からRDBMS接続における注意点と接続パターン RDS Proxy

その3:FaaS基盤からRDBMS接続における注意点と接続パターン DataAPI/DataAppとTiDB Serverless Driver

前回の記事では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関数のリファレンスはこちらにあります。

https://www.prisma.io/docs/orm/prisma-client/queries/crud

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-