ORMで使用されるデザインパターンについてnpmパッケージを元に考える
概要
パターンについて
大抵のORMは、これらのうち複数のパターンを実装しています DB上のテーブルへのアクセスを抽象化する
このパターンによってDBへのアクセスを抽象化することで、例えば、SQLを書かなくてもクエリが実行できたり、RDBMS間の差異を吸収することなどができます code:prisma.ts
const user = await prisma.user.findUnique({
where: { id: 1234 },
});
await prisma.user.update({
where: { id: 1234 },
data: { name: "foobar" },
});
DBのレコードと1:1に紐づき、永続化ロジックを提供するオブジェクトを実装するパターン
code:javascript
const user = await User.find(1234);
user.name = "foobar";
await user.save();
Model(ここではリレーションにマッピングされるオブジェクトの意味)が永続化に関する知識を持つ
code:sequelize.js
const user = await User.findByPk(1234);
user.changeName("foobar");
await user.save();
Model(リレーションにマッピングされるオブジェクト)はドメインロジックを持つものの、永続化に関する知識を持たないのがActive Recordとの違い ただし、ドメインと永続化を完全に分離することは難しくて、大抵のORMではある程度妥協されている場合がほとんどだと思います このうちデコレータを使ってマッピングを定義した場合、Modelが永続化に関する知識(TypeORMに対する依存)を持つことになるため、完全にドメインと永続化を分離できているわけではなくなります MikroORMの場合、*:N関係を表現するのに独自のCollection型を使用する必要があり、ドメインモデルからORMへの依存ができる 永続化に関する責務は、ORMにもよりますが、EntityManagerやRepositoryのような名前のクラスで提供されることが多い印象です (これらのクラスは、ほとんどの場合、テーブルデータゲートウェイパターンを実装しているはずです) code:typeorm.ts
const userRepository = dataSource.getRepository(User);
const user = await userRepository.findOneBy({ id: 1234 });
user.changeName("foobar");
await userRepository.save(user);
各ORMが実装しているパターンについて
結局どのパターンがいいの?
ビジネスロジックをどのように実装するかにもよる
特にPrismaはAPIもシンプルで学習コストも他のORMと比較しても低めだと思うため、生産性もかなり意識して作られていると思うため、とにかく動くものを素早く作りたいケースにおいては向いているはず。 きちんとテストが書かれて自動化されていれば、結局どちらのパターンを使用したとしてもメンテナンスは十分可能だと思います。
なので、この2つのパターンについては、対象ドメインの複雑度によってどちらを使うかを決めるとよいのではないかと思います。
また、チーム内にRailsのスキルを持ったメンバーが多く在籍しているような場合は、Railsでactiverecordを使って実装した方が、生産性やメンテナンス性の面では実は有利かもしれません。(最近、何かと批判されがちな印象は感じるものの、Railsは非常に枯れており、実績も十分にあるとても優れたフレームワークだと自分は思っています) 最終的にはどのパターンを採用するにせよ、保守性の観点からすると、結局はテストの自動化やCIとかの仕組みとかが重要なのではないかと思います
参考
関連ページ