TypeScript で Node.js やる場合の O/R Mapper の選択肢は少ない。
メジャーっぽかったのと Nest.js で標準対応してるので TypeORM を使うことにしてみた。機能的には充分だが、本番環境での運用を想定した使いやすさには至ってないように感じた。
✍️ TypeORM の機能
- Entity クラスをつくってプロパティにデコレータを付けるだけでテーブル定義できる
- Ruby on Rails の ActiveRecord に近い感覚で使うこともできる
-
@PrimaryGeneratedColumn で
AUTO_INCREMENT
なプライマリキー -
@CreateDateColumn で
INSERT
時に Date カラムを自動更新 -
@UpdateDateColumn で
UPDATE
時に Date カラムを自動更新 - ActiveRecord の Model と同じように Entity クラスに READ/WRITE メソッドを付加できる
-
@PrimaryGeneratedColumn で
- 実行された SQL の出力など柔軟な Logging 設定ができる
-
マルチデータベース対応
- レプリケーションとして master/slave の指定もできる
- 外部キー制約の活用
- CLI ツールによる Entity クラスの生成や Migration ファイルの生成
😇 ここがダメだよ TypeORM
使いにくいと感じたところ。
CREATE TABLE をマイグレーションで管理しにくい
TypeORM では CLI ツールが提供されており、マイグレーションを管理する仕組みがある。
$ yarn global add typeorm
$ typeorm -v
0.2.7
CLI ツールで sync
すると自分が定義した Entity クラスがすべて CREATE TABLE
されて DB 反映される。
$ typeorm schema:sync
ただしこの実行内容は migrations
テーブルで管理されないため、再実行するたびに ALTER TABLE
が実行されてしまう。本番環境でこのコマンドはちょっと使えない。
マイグレーションファイル側で CREATE TABLE
を記述することもできるが、それだとテーブル定義が Entity クラスとの二重管理になってしまうし、そもそも文法覚えるのもだるい。
ORM という性質上、テーブルの中身と Entity クラスを自動マッピングしてくれる役割なわけで、Entity クラスを作らないという選択肢はないしね。
CLI ツールで気軽に全テーブル削除できる
$ typeorm schema:drop
これだけで全テーブルが問答無用に削除される。明らかにテスト環境用のコマンドだけど、ターミナルのタブを別環境と間違えてしまい、うっかり本番環境で実行したりしそうで怖い。こういうのはデフォルトで --dry-run
になってほしいが、DryRun のオプション自体がない。
🚑 本番運用するための対応策
マイグレーションについては CREATE TABLE
含めてマイグレーションファイルで管理したい。
少しトリッキーかもだが下記方法で実現できた。
sync
はローカル環境だけで行う
sync
すると CREATE 文がコンソールに出力されるのでコピーしておき、その内容をマイグレーションファイルにペーストする。
$ typeorm migration:create -n CreateUser
async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query('CREATE TABLE `users` (`id` int NOT NULL AUTO_INCREMENT, `created_at` datetime(0) NOT NULL DEFAULT NOW(), `updated_at` datetime(0) NOT NULL DEFAULT NOW(), PRIMARY KEY (`id`)) ENGINE=InnoDB');
}
async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query('DROP TABLE IF EXISTS `typeorm_test`.`users`');
}
Entity クラスで synchronize: false
にする
@Entity({name: 'users', synchronize: false})
export class User {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({name: 'created_at', precision: 0, default: () => 'NOW()'})
createdAt: Date;
@UpdateDateColumn({name: 'updated_at', precision: 0, default: () => 'NOW()'})
updatedAt: Date;
}
こうしておけば以後 sync
コマンドの対象外となり、本番環境で schema:sync
と schema:drop
を使う必要がなくなる。
マイグレーション実行は設定ファイルで migrationsRun: true
にしておけばサーバ起動時に実行される。トランザクション使ってテーブル生成やテーブル変更が行われるので、難しいこと考えなくても新しいサーバをデプロイするだけでよくなる。
package.json
サーバ起動は yarn start
するだけでよい。prestart
から自動実行される。
"scripts": {
"prestart": "rm -rf dist && tsc",
"start": "node dist/main.js"
}
🗽 まとめ
TypeORM は必要な機能すべて揃ってはいるが、ホスピタリティというか開発者が便利につかえるというところまでは手が回ってないという感じ。
Issue も500件くらい溜まっていて、その半分はレスがついていない。需要と比べて開発者がかなり不足しているようだ。