🦔

Drizzle ORM利用時に直面した課題と解決方法

2024/10/31に公開

はじめに

こんにちは。株式会社トリドリでバックエンドエンジニアをしている松田です!

前回まででDrizzle ORMの概要やマイグレーションの仕組み構築について書いてきました。

前回の記事
https://zenn.dev/toridori/articles/9c41d1fd7f6a85

https://zenn.dev/toridori/articles/7ea35472f8a30c

今回はDrizzle ORMを運用する中で直面した課題とその解決策を一部紹介できればと思います。
実際に運用で使われている方や、これから使ってみたいからどんな課題や解決方法があるのかを事前にチェックしておきたい人向けに書いています!
概要や導入方法については前回の記事を参照ください。

運用時の課題と解決方法

今回は以下の課題について見ていきます。

1. 自動で生成される外部キーの名前が字数制限を超えてしまう
2. トランザクションの処理の中で、生成したレコードのidを使用したい
3. seedの仕組みって本当にないのか?
4. (おまけ)Drizzle ORMが用意しているDB操作ツールの紹介

※MySQLを使用した例で書いています
※間違っている箇所があれば教えていただけると助かります
※2024年10月31日現在の情報です

自動で生成される外部キーの名前が字数制限を超えてしまう

例えば employee_performance_reviewsperformance_metricsの二つのテーブルがあったとします。(適当に考えました)
従業員のパフォーマンスレビューの記録と、パフォーマンス評価の指標が格納されるテーブルです。

employee_performance_reviewsテーブルはperformance_metricsテーブルのidを参照する外部キーを持っているとします。

この場合、通常の外部キー制約の書き方で書くと以下のようになります。
わかりやすくするため最低限のカラム定義で書きます。

export const employeePerformanceReviews = mysqlTable("employee_performance_reviews", {
  id: int("id").primaryKey().autoincrement(),
  performanceMetricId: int("performance_metric_id")
    .references(() => performanceMetrics.id)
    .notNull()
    .unique(),
});

export const performanceMetrics = mysqlTable("performance_metrics", {
  id: int("id").primaryKey().autoincrement(),
});

外部キー制約の記述についてのドキュメント
https://orm.drizzle.team/docs/indexes-constraints#foreign-key

この定義のもと作られる外部キーの文字数は76文字となり、MySQLの制約の文字数である64文字を超えてしまい、以下のような指摘が返されます。

Error: Identifier name 'employee_performance_reviews_performance_metric_id_performance_metrics_id_fk' is too long

以前railsを使っていた時にも同様なエラーに直面したことがあったことを思いだしました。

Drizzle ORMでは以下のようにして独自に外部キーの設定を行うことができ、名前も自分でつけることができるようになります。要は自動で作ってもらっていたのを手動で定義して作りましょうということです。

export const employeePerformanceReviews = mysqlTable("employee_performance_reviews", {
  id: int("id").primaryKey().autoincrement(),
  performanceMetricId: int("performance_metric_id")
    // referencesの定義を消します
    .notNull()
    .unique(),
}, (table) => {
  return {
    parentReference: foreignKey({
      columns: [table.performanceMetricId],
      foreignColumns: [performanceMetrics.id],
      name: "emp_perf_reviews_metric_fk"
    }),
  };
});

// 参照されるテーブルの定義はそのまま変わらず
export const performanceMetrics = mysqlTable("performance_metrics", {
  id: int("id").primaryKey().autoincrement(),
});

【公式ドキュメント】外部キー制約に独自で名前をつける書き方

これで制約の文字数制限を解消できました!

トランザクションの処理の中で、生成したレコードのidを使用したい場合

トランザクション処理をしていて、作成したレコードのidを使いたい場合があるかと思います。
その際のシンプルな書き方があったので紹介します。

まず最初に、普通に書いた場合の例は以下のようになります。

await db.transaction(async (tx) => {
    await tx
        .insert(users)
        .values({ userId: userId })

    // この後ろで作成したusersのレコードを再取得するselect句を書いてidを取得する
    // もしくはinsertから返されるオブジェクトを変数に格納しinsertIdを取得する
    const user = await tx
        .insert(users)
        .values({ userId: userId })
    console.log("userId", user[0].insertId);
}

以下はclientsというテーブルで試した例ですが、insertによって返されるオブジェクトはこのようになっています。

上記のように書いても問題はないですが、$returningId()を使うとinsertの結果でinsertIdが取得できるため簡潔に書くことができます。

await db.transaction(async (tx) => {
    const [{ id }] = await tx
        .insert(users)
        .values({ userId: userId })
        .$returningId();

    // 変数idに、insertしたusersのidが格納されている
});

【参考】$returningIdについて

なお、$returningId()はMySQLドライバの時だけ使用でき、PostgreSQLもしくはSQLiteを使用しているときは、returningとして特定のカラムを返す書き方が使えます。
以下の場合はidを持ったuserオブジェクトが取得できています。

const [user] = await tx.insert(users).values({
  name: "hoge taro",
  email: "hoge_taro@example.com",
}).returning({ id: users.id });

他のカラムも欲しい場合は以下のようにすることで、追加したカラムを持つuserオブジェクトを返すことができます。

const [user] = await tx.insert(users).values({
  name: "hoge taro",
  email: "hoge_taro@example.com",
}).returning({ id: users.id, name: users.name, email: users.email });

【参考】returningについて

これで簡潔なトランザクションを書くことができるようになります。

seedの仕組みって本当にないのか?

改めて公式を見てみましたが、現時点で公式提供のものはありません。しかしdrizzle-seed というpackageが今後リリースされる予定と書かれていました👀

https://orm.drizzle.team/docs/kit-seed-data

前回の記事ではseed.tsを作ってseedデータのクエリを書いてtsxで実行するように構築しました。
調べてみるとyoutubeでも同様に書かれている人がいました。こちらの方はテーブルごとにseedファイルを作成し、それを一つのファイルにまとめて実行コマンドを打つというやり方をされています。
`
https://www.youtube.com/watch?v=vyU5mJGCJMw&t=3140s

ではこのやり方以外にはないのかというと、公式から手動でカスタムマイグレーションファイルを作成してそこにseedデータを投入する記述を書いてgenerete→migrateするといいよと書かれていました。

https://orm.drizzle.team/docs/kit-custom-migrations

このやり方でもいいかと思います。
ただgenerate→migrateを使わずpushを使っている場合はこのフローは使用できないので、この場合はやはりseed.tsを使って実行コマンドに組み込むのが良いかなという現状ですね。

drizzle-seed packageのリリースを期待して待ちましょう!

(おまけ)Drizzle ORMが用意しているDB操作ツールの紹介

僕はよくMySQL Workbenchを使っているんですが、Drizzle ORMにはDrizzle studioという便利なデータベース管理ツールが用意されています。

https://orm.drizzle.team/drizzle-studio/overview

ドキュメントにもある通り、既にデータベースの設定が済んでいればすぐに使えるようになるのでおすすめです。シンプルで見やすく使いやすい点がおすすめです。Drizzleの軽量さが現れているようで個人的に好きです。

公式が出しているデモ環境があるので、以下から操作感を試して遊んでみてください!
https://demo.drizzle.studio/

まとめ

今回は以下の課題について解決策を見ていきました!

1. 自動で生成される外部キーの名前が字数制限を超えてしまう
2. トランザクションの処理の中で、生成したレコードのidを使用したい
3. seedの仕組みって本当にないのか?
4. (おまけ)Drizzle ORMが用意しているDB操作ツールの紹介

最近drizzle公式のドキュメントが大幅に改良されて、図解や説明が前よりも分かりやすくなってい流ように感じます。何ができるのかを調べるときに公式ドキュメントを見るのが1番分かりやすいかもしれません。ただ、全部に目を通すのは余程余裕がないと難しいと思うので、今回のような記事でかいつまんでトピックを拾っていただいて詳細を追っていただくといいのかなと思います。

ここまで読んでいただきありがとうございました!何か学びになれましたら幸いです。
それではまた!

参考文献

【Drizzle ORM】外部キー制約に独自で名前をつける書き方

【Drizzle ORM】トランザクションの書き方

【Drizzle ORM】$returningIdについて

【Drizzle ORM】returningについて

【MySQL】識別子の長さ制限

toridori tech blog

Discussion