AWS CDKのNodejsFunctionでPrismaをバンドルしてLambdaへデプロイする

AWS CDKのNodejsFunctionでPrismaをバンドルする
2021.02.28

はじめに

CX事業本部の佐藤智樹です。

今回はAWS CDKのLambda用のバンドル機能のNodejsFunctionを使用してPrismaをバンドルする方法を紹介します。公式ではServerless Frameworkでデプロイする方法が紹介されていますがAWS CDKはなかったので紹介します。基本的には公式で行っている作業をAWS CDKに置き換えただけです。

本記事は、タイトル通りAWS CDKのNodejsFunctionでPrismaをバンドルしたい方向けの内容です。ただPrismaだけでなくNodejsFunctionにバンドルされないファイルをデプロイパッケージに含みたい場合にも参考になるかと思います。

AWS CDKのNodejsFunctionはまだ実験的な機能なので、今回紹介するバージョンでのみ動作を確認しています。少し前にバンドラーがparcelからesbuildに変わったり今後バンドル周りの設定は変わる可能性があるのでその点はご了承ください。問題がある場合は、公式ドキュメントAWS CDKのソースをご確認ください。

環境情報

種別 バージョン
AWS CDK 1.86.0
Prisma 2.16.0
Node 12.x.x

公式資料と今回やる内容の紹介

先にServerless Frameworkでやっている公式ドキュメントを載せておきます。

読んだだけだとぱっと見ではわかりませんが実際に試すと2ファイル追加でバンドルする必要があります。

  • スキーマ定義: prisma/schema.prisma
  • クエリエンジン: query-engine-rhel-openssl-1.0.x

schema.prismaはprisma generate時にnode_modulesに配置され、クエリエンジンは指定すればnode_modulesに配置されます。webpackやesbuildなどで関連するモジュールだけバンドルする場合、デプロイパッケージに含まれないので個別に入れる必要があります。

実践

本章からは実際にAWS CDKに設定した内容を紹介します。前提としてLambda on VPCでAurora(Postgres)との接続を念頭におき、VPCやAuroraの設定は既に存在していて、AWS CDK側では呼び出しのみ行う前提で紹介します。

Prisma側の設定

Prismaのインストール方法やDB接続の初期設定は以下のチュートリアルを参考にしてください。

公式ドキュメントへ書かれているように、ローカル環境とLambda環境で使用できるクエリエンジンが異なるのでschema.prismaファイルにエンジンを追加でいれる設定を追記します。binaryTargetsの部分です。rhel-openssl-1.0.xのクエリエンジンがLambdaで必要となり、ローカルでの実行にはnativeの設定が必要になります。

prisma/schema.prisma

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
  binaryTargets   = ["native", "rhel-openssl-1.0.x"]
}

model sample {
...

上記の設定でprisma generateを実行するとLambdaで実行する用のクエリエンジンがnode_modules内に追加されます。こちらをデプロイパッケージに組み込むように設定します。

AWS CDK側の設定

AWS CDK側ではschema.prismaquery-engine-rhel-openssl-1.0.xを組み込みます。具体的には、NodejsFunctionのcommandHooksでバンドル時に追加でコピーコマンドを実行してデプロイパッケージに投入します。

bin/lambda-prisma-stack.ts

import { Stack, Construct, StackProps, Duration } from "@aws-cdk/core";
import { Vpc, Subnet, SecurityGroup } from "@aws-cdk/aws-ec2";
import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";
import { Runtime } from "@aws-cdk/aws-lambda";

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

    const vpc = Vpc.fromLookup(this, "prisma-vpc", {
      vpcName: "prisma-main-vpc",
      isDefault: false,
    });
    const lambdaSecurityGroup = SecurityGroup.fromLookup(
      this,
      "prisma-lambda-security-group",
      "sg-prisma01"
    );

    const lambdaSubnets = [
      Subnet.fromSubnetAttributes(this, "LambdaPrivateSubnet1a", {
        availabilityZone: "ap-northeast-1a",
        subnetId: "subnet-prisma01",
      }),
      Subnet.fromSubnetAttributes(this, "LambdaPrivateSubnet1c", {
        availabilityZone: "ap-northeast-1c",
        subnetId: "subnet-prisma02",
      }),
    ];

    const lambda = new NodejsFunction(this, "prisma-lambda", {
      entry: "./src/lambda/handler.ts",
      handler: "handler",
      runtime: Runtime.NODEJS_12_X,
      functionName: "prisma-lambda",
      memorySize: 128,
      vpc,
      vpcSubnets: vpc.selectSubnets({
        subnets: lambdaSubnets,
      }),
      securityGroups: [lambdaSecurityGroup],
      environment: {
        // DB接続URLを格納しているSSMパラメータ名などを設定
        SECRETS_NAME: "/prisma/Secrets"
      },
      bundling: {
        // commandHooksでインストール前、バンドル前、後にコマンドを組み込める
        commandHooks: {
          beforeInstall(inputDir: string, outputDir: string): string[] {
            return [``];
          },
          beforeBundling(inputDir: string, outputDir: string): string[] {
            return [``];
          },
          afterBundling(inputDir: string, outputDir: string): string[] {
            return [
              // クエリエンジンを追加
              `cp ${inputDir}/node_modules/.prisma/client/query-engine-rhel-openssl-1.0.x ${outputDir}`,
              // スキーマ定義を追加
              `cp ${inputDir}/prisma/schema.prisma ${outputDir}`,
            ];
          },
        },
      },
    });
  }
}

上記のソースに書いたようにafterBundlingなどのバンドル実行時の周辺の処理にコマンドを組み込むことで、バンドラーのバンドル対象とならないファイルも含めることができます。筆者の場合は上記の設定でPrisma経由でAurora(Postgres)へのクエリ実行ができるようになりました。

感想

VPC、Aurora(Postgres)も含めてコード化して記事にしようと思っていたのですが中々面倒でお蔵入りにしそうだったので、AWS CDKとPrisma部分だけでも紹介したらためになるかもと思い記事にしました。今後余力があれば整理してGitHub上に公開します。

Prisma自体はGROUP BYや複数INSERTなどを実行する場合はまだ機能がPreview版ですが基本的なCRUD処理などは問題ないです。Node.jsのORMとして候補に入るとは思うので是非試して見てください。