TypeORMを使って一通りアプリケーション開発を行ってみました。ちょっとした事はその都度記事に書いていましたが、一旦ここで全体的にどうだったかを一通りまとめてみたいと思います。
使用したのはTypeORM 0.2.12です。
Node.jsだと以前Sequelizeで他のアプリケーションを作成した事がありますが、それと比べてどうだったかという観点でも見てみた…というつもりでしたが、僕が使っていたのがSequelize4.38だったので、もしかしたら色々変わっていて使いやすくなっている可能性もありそうかな、という気もしました。マニュアルを見るとTypeScriptの例も載っているようですし。また誰か最新のもの同士を比較した記事などを書いてみてほしいところです。
プログラムの書き方が最近よくあるORMとほとんど同じのため、直感的に書いていくことができました。例えばインスタンスを作成して保存する時は下記のような感じです。
import Post from './models/Post'
const post = new Post()
post.user = user
post.body = req.body.body
await post.save()
データを取得して使うときも下記のような感じです。
const post = await Post.findOneOrFail(req.query.id)
console.log(post.body)
他の言語のORMを使ったことがある人であればすぐ使いこなせる感じがしました。なにより普通にimportでモデルを使えるのが良かったです。
SequelizeだとExpress側に読み込んだモデル群をsetしておいてそこから使わなくてはならなかったのでいちいちコードが長くなっていて、ずっと不満を抱えてたような記憶があります。(もしかしたらそのあたりですかね、一番の不満は…)
TypeScriptのため、当然のことながら補完もきっちり効いてくれ、何も考えずにどんどん書いていけます。
Entityのstaticメソッドから直接データを取得する場合と、クエリビルダを作って取得するパターンがありました。
staticメソッドを使う方法だと、下記のようにメソッド一つでデータを取得するような形になります。
const posts = Post.find({
relations: ['user'],
where: {
mode: 'public'
},
order: { createdAt: 'DESC' },
take: 10
})
使い回しとかはできそうにないのでちゃちゃっと決まりきったデータを取るような感じでしょうか。あまり複雑なクエリで使うとあとあと面倒になりそうです。一方、クエリビルダは
const likes = await createQueryBuilder(Like)
.where({ answer, enabled: true })
.getMany()
のようによく見るような形で書けます。ちょっと複雑になる場合は基本的にはこっちで書いていったほうが良さそうです。どちらの書き方でもちゃんと補完とかチェックは効きます。
マイグレーションは直接書く方法と、下記記事のようにsyncをうまいこと使ってなんとかしよう、という感じの方法がありそうで、どういう形がベストなのか今のところ分かっていません。
TypeORMで本番運用を見据えたマイグレーション - Qiita
とはいえなんとなくこれは微妙な感じもしますし、今回は他のフレームワークと同様に普通にマイグレーションを作って実行していく感じで作りました。
ただ、TypeORMはEntityにカラム情報も設定するため、なんとなく2重管理になってしまうようなイメージはあります。でも他もだいたい同じような感じでシンプルな感じがしますし、特に大きなデメリットではないかな…と思いとりあえずこの形にしました。実際皆さんどんな感じで運用しているのかは気になるところです。
Expressのルーティングのテストの場合モデルも動かす必要が出てきますが、このあたりもいまいち使い勝手がわかっていません。LaravelやRailsなどであれば各テスト毎にいい感じにデータをリセットしてくれたりするのですが、TypeORMは特にフレームワークと密接に結びついているわけではないのでなんとなく自分でそのあたりも実装していく必要があります。
今回試したのは、Laravelのようなfakerを使ったfactoryをむちゃくちゃ雑に作成し、それでその都度データを作成してテストするようにしました。下記がfactoryの例です。
const faker = require('faker')
import { Question } from '../entity/Question'
import UserFactory from './UserFactory'
export default class QuestionFactory {
static async create(data = {}, relations?) {
const entityData: any = Object.assign(
{
uniqueId: faker.random.uuid(),
name: faker.random.word(),
answerCount: faker.random.number()
},
data
)
if (relations && relations.user) {
entityData.user = relations.user
} else {
entityData.user = await UserFactory.create()
}
const question = Question.create(entityData)
await question.save()
return question
}
}
実際に下記のように使ってテストします。
it('GET /:id', async () => {
const question = await QuestionFactory.create()
await UserSocialFactory.create({}, { user: question.user })
return await request(app)
.get(`/api/questions/${question.uniqueId}`)
.expect(200)
})
これであればデータの不整合も特に起こりません。データは残ってしまいますが、特に頻繁にローカルで試すわけでなければ特に問題ないので、支障が出たらデータクリアすればいいかな…と思っています。CIだとデータは使い捨てのため全く問題はありませんし。
なんかデータのリセットを入れたいなとも思ったのですが、うまいこと速く実行する方法を見つけられなかったため諦めました。なんとかしてCLIの機能を呼んでも良いのかな、とも思ったのですが毎回時間をかけてマイグレーションしたりするのもなんとなく微妙かなとも思いました。
まあでもこの辺はTypeORMというよりも、パッケージを組み合わせていって構築するNode.js & Express自体の問題かもしれませんが。
バージョンが0.2のため、1.0リリース時には結構色々変わってる可能性もあるかな…という気はします。個人開発やスタートアップのMVPでぱぱっとつくる時なら良いと思いますが、クオリティ重視のじっくり開発するアプリケーションだとまだ早いかな…というイメージもあります。まあじっくり時間を書けてメンテナンスしていく予定ならそれでも良いのかもしれませんが。
あとマニュアルも色々足りませんね。ソースを見てオプションなどを把握したりする必要はあったりします。
Sequelizeや他のパッケージでTypeScriptをやっていないので比較できてないのでなんとも言えないところですが、今のところ使ってみた感じとしては好みの部類です。
ただ、まだ不足している機能など、物足りない部分も段々と出てきそうな気がするので、むしろ1.0までContributeして応援していく勢いで使っていくというのもありな気がします。全然人手も足りていないようですし。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント