TypedSQLはとても便利だが、ビルド時にDB接続が必要になって困った話
はじめに
Prismaを使い始めて久しいですが、集計画面などはどうしても複雑なクエリを投げる必要があり苦渋の思いで$queryRaw
を使いがちです。
ですが、最近以下の記事を見かけました。
こんな便利なものがあるのかと使い始めたのですがビルド時にハマり、その後長いこと使用を諦めていたのですが、この度なんとか解決できたので記事にします。途中試行錯誤しながらのチャレンジでしたが、終わってみればあっさりとした解決方法でした。
前提条件
以下のような作りのプロジェクトがありました。
- モノレポ構成(ここでは簡単のために
packages/cdk
とpackages/server
の2つとする)-
packages/server
はNextでしたが、本筋にはあまり関係ないです
-
-
packages/cdk
をデプロイすることで、AWS ECSにコンテナとしてpackages/server
をデプロイ - そのため
packages/server
はDockerfileでビルドしている - CDKではDockerImageAssetを使用
コードのイメージ
まずはDockerfileです。だいぶ端折ってますが、prisma generateをしてNextをビルドして起動しています。
TypedSQLを使いたいのでyarn prisma generate --sql
を実行しています。
FROM public.ecr.aws/docker/library/node:lts-slim
# ...割愛するが色々とapt-get intallして作業ディレクトリを指定
# Prismaまわりの処理
RUN yarn prisma generate
# TypeSqlのgenerate
RUN yarn prisma generate --sql
# アプリケーションをビルド
RUN yarn build
# ポート設定
EXPOSE 3000
# 起動
CMD ["yarn", "start"]
続いてCDKです。先ほどのDockerfileにパスを向けてビルドした後、それをタスク定義に追加しています。
import { DockerImageAsset } from "aws-cdk-lib/aws-ecr-assets";
// ...略
const taskDef; // タスク定義(作成部分は省略)
// コンテナイメージのビルド
const nextjsImage = new DockerImageAsset(this, "NextjsImage", {
directory: "先ほどのDockerfileへのパス",
buildArgs: {
// ビルド時に必要そうな環境変数をあれこれ
},
});
// コンテナをタスク定義に追加
const container = taskDef.addContainer("NextjsContainer", {
// 先ほどビルドしたイメージを指定
image: ContainerImage.fromDockerImageAsset(nextjsImage),
logging: LogDrivers.awsLogs({ streamPrefix: "Nextjs" }),
environment: {
// DB接続はビルド時でなくECSにデプロイした後で行うのでここで設定
DATABASE_URL: "CDKで作成したRDSの接続情報",
},
command: [
"sh",
"-c",
"yarn start",
],
});
なにが起きたか
Dockerfileにyarn prisma generate --sql
が加わったことで、CDKデプロイ時に下記のエラーが出るようになります。
error: Environment variable not found: DATABASE_URL.
--> prisma/schema.prisma:16
|
| provider = "postgresql"
| url = env("DATABASE_URL")
これはschema.prisma
の中で指定しているDATABASE_URL
が参照できないことで発生します。
補足:DockerImageAssetについて
先ほど使用しているDockerImageAsset
ですが、CDKのデプロイ(Cfnスタックのデプロイ)より先にコンテナイメージをデプロイします。なので処理順としては
- cdk deployを実行したプロセス上でDockerfileに基づきコンテナイメージをビルド
- CDKの記述に従ってデプロイ
の順番です。なのでDockerImageAsset
のbuildArgs
に「CDKをデプロイしないと確定しない値(eg. リソースARNなど)を含めることはできない」という痛い制約があります。
エラーの原因
DATABASE_URL
もCDKで作成されるRDSの接続情報なので、ビルド時に参照できないのですがTypedSQLを用いない場合は このエラーは発生しません 。
これはprisma generate
は実際のDBではなくSchemaだけを参照するのに対して、prisma generate --sql
はDB接続を必要とするためです。
これは公式にも記述があります。
TypedSQL requires an active database connection to function properly.
This means you need to have a running database instance that Prisma can connect to when generating the client with the --sql flag.
If a directUrl is provided in your Prisma configuration, TypedSQL will use that for the connection.
要するにprisma generate --sql
を使用する場合は、そのプロセスから参照できるDBが配置されている必要があります。
今回のケースでいうところのRDSがそれにあたるのですが、大概のRDSはVPC内のプライベートサブネットの中に入ってるのでビルドプロセスからは接続できないと思います。何なら、初めてのCDKデプロイの場合はまだRDSが作成されていないと思います。
対策:ダミーのDBを作って、それを参照してもらう
言い換えれば「prisma generate --sql
を実行する時だけDB接続ができればいい。それはダミーDBでも可」ということになるので、今回はその方針を取りました。
CDKのデプロイをローカルで行っている場合はlocalhostでDBを立ててそれを参照させればOKですし、GitHub Actionsなどを使っている場合はymlの中のservices
を使って一時的にDBを立てればOKです。
それを踏まえて先ほどのDockerfileとCDKは以下のように変わりました。
FROM public.ecr.aws/docker/library/node:lts-slim
# ...割愛するが色々とapt-get intallして作業ディレクトリを指定
# ダミーDB用の接続情報
ARG DATABASE_URL
# Prismaまわりの処理
RUN yarn prisma generate
# TypeSqlを使用時に一時的にDBに接続する必要があるのでダミーDBにマイグレーションをかける
RUN DATABASE_URL=$DATABASE_URL yarn prisma migrate deploy
# TypeSqlのgenerate
RUN DATABASE_URL=$DATABASE_URL yarn prisma generate --sql
# アプリケーションをビルド
RUN yarn build
# ポート設定
EXPOSE 3000
# 起動
CMD ["yarn", "start"]
import { DockerImageAsset, NetworkMode } from "aws-cdk-lib/aws-ecr-assets";
// ...略
// コンテナイメージのビルド
const nextjsImage = new DockerImageAsset(this, "NextjsImage", {
directory: "先ほどのDockerfileへのパス",
buildArgs: {
// 一時的にダミーDBの接続情報を渡す
DATABASE_URL: process.env.DATABASE_URL!,
},
// ビルド時に一時的にダミーDBと導通するため、NWはホストのものを利用する
networkMode: NetworkMode.HOST,
});
// ...略
DockerfileではRUN
の中で環境変数を設定することで一時的にその値を利用するようにしています。
CDKはDockerImageAsset
のnetworkMode
をホストに向けて、ホスト(ビルドプロセス)にあるダミーDBと接続できるようにしています。
あとはcdk deploy
時に環境変数に設定 or コマンドライン引数で指定、のどちらかを行えばOKです。
まとめ
Prismaは数あるORMの中でも使いやすく気に入っていますが、複雑なロジックを伴う場合はどうしてもSQLを書く必要があります。
その際にいつも泣く泣く$queryRaw
を使い、実行結果がany
になってしまうのを受け入れるしかなかったですが、TypedSQLで随分と状況が改善しました。
ただ、そのしわ寄せがビルド・デプロイ時に来てしまい、しかも具体的な対処法がググっても見つからなかったので記事にした次第です。
今回の内容が役立ちましたら幸いです。
Discussion