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