2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PrismaはORMなのか?(Is Prisma ORM an ORM?)

Last updated at Posted at 2025-01-22

はじめに

意味深なタイトルですが、このタイトルはPrismaの公式ドキュメントに存在するページタイトルです。

弊社では2023年度より新規プロジェクトにおけるサーバサイドの実装を従来使用していたRuby on RailsからTypeScript/NestJSに移行しており、ORMはPrismaを採用しています。

ORMの選定にあたっては他にもSequelizeやTypeORMが候補に挙がりましたが、最終的にはPrismaを採用し、現在に至るまで使用しています。

しかしながら選定の中でPrismaの仕様に違和感を感じ、その違和感を一言で表現するのであれば「これはORMなのか?」というものでした。

この違和感を解消する過程でまさに直接的な答えとなるような公式ドキュメントに出会い、さらに自分自身のORMというものに対する認識が偏ったものであるという気づきがありました。

特に、昨今のモダンなWebアプリケーションフレームワークであるRuby on Rails(Active Record)やLaravel(Eloquent)などを介してORMというものに対する認識を固めている方に向けて参考となれば幸いです。

なぜPrismaに違和感を感じたのか

まず最初に違和感を感じたのはPrisma ORMが返す値はプレーンなJavaScriptオブジェクトであるという点。
公式ドキュメントの冒頭には以下のような記述がある。

Prisma ORM works fundamentally different compared to that. With Prisma ORM, you define your models in the declarative Prisma schema which serves as the single source of truth for your database schema and the models in your programming language. In your application code, you can then use Prisma Client to read and write data in your database in a type-safe manner without the overhead of managing complex model instances. This makes the process of querying data a lot more natural as well as more predictable since Prisma Client always returns plain JavaScript objects.

要約すると、

  • 従来のORMとは根本的に異なる新しい種類のORMであり、従来のORMに関連する多くの問題を抱えていない
  • 従来のORMはリレーショナルデータベースとオブジェクト指向プログラミングの間のマッピングを提供するが、オブジェクト-リレーショナル間のインピーダンスミスマッチによる多くの問題が発生する
  • Prisma ORMは、宣言的なPrismaスキーマでモデルを定義し、これがデータベーススキーマとプログラミング言語のモデルの唯一の情報源となる
  • アプリケーションコードでは、Prisma Clientを使用して型安全にデータベースの読み書きを行い、複雑なモデルインスタンスの管理を不要にする
  • Prisma Clientは常にプレーンなJavaScriptオブジェクトを返すため、データのクエリがより自然で予測可能になる

前半部分でPrismaが従来のORMとは異なるアプローチをとっており、そのアプローチには大きなメリットがあるという主張は理解できた。
しかし、Active Recordに長らく親しんできた身としてはORMが返す値はModelクラスのインスタンスであるというイメージが強く、ORMとして機能提供をする最善の手法がプレーンなJavaScriptオブジェクトを返すという部分が個人的にはどうしてもしっくりこなかった。

Modelクラスの実装をどのように置き換えるか

ORMが返す値はプレーンなJavaScriptオブジェクトであるということは、Active Recordなどの"従来のORM"においてModelクラスで実装していたものをどのように置き換えるかということを考える必要がある。

例えば、カラムを拡張した属性を持たせるような以下の実装。

user.rb
class User < ApplicationRecord
  def full_name
    "#{first_name} #{last_name}"
  end
end

このユースケースはPrisma Client extensionsで置き換えることができる。

user-extension.ts
import { Prisma } from '@prisma/client'

export const userExtension = Prisma.defineExtension((client) => {
  return client.$extends({
    result: {
      user: {
        fullName: {
          needs: { firstName: true, lastName: true },
          compute(user) {
            return `${user.firstName} ${user.lastName}`
          }
        }
      }
    }
  })
})

しかしながらActive Recordのモデルクラスではこのようなケース以外にも、以下のようなビジネルロジックを相当数実装することが多い。

  • Validation
  • Scope
  • Callback
  • Associations

先ほどの例と同様、それぞれのビジネスロジックを個別に切り分けていくと、Prismaの機能や使用するフレームワーク、その他のライブラリを組み合わせることで置き換えていくことはできるが、やはりActive Recordに慣れていると「Modelクラスを実装した方が早くないか?」となってしまう。

ビジネスロジックを本当にModelクラスに実装すべきか

この点はRuby on Railsでも有名なファットモデル問題として取り上げられており、様々な意見がある部分ではあると思うが、個人的にはValidationについては本当に宣言的なバリデーションをModelクラスに書くことが最善の方法なのか?という点は考えるべき部分だと思う。

Modelクラスに宣言的なバリデーションを実装するということはデータベーススキーマを主眼に置いたものとなるが、フレームワークによってはControllerあるいはGraphQLにおけるResolverの手前のレイヤーで扱うべきという考え方もあり、この場合はFormあるいはWebAPIとしての仕様を主眼に置いたものとなる。
しかし、実際のアプリケーション開発においては両方の観点でのバリデーションが求められ、これを一箇所に集約することが最適なのかという部分には疑問が残る。

つまり、バリデーションに限らず従来モデルクラスに集約していたビジネスロジックは使用するフレームワークやREST、GraphQLなどのAPIアーキテクチャ、またはアプリケーション自体の要件や全体の設計に応じて適切なレイヤーを定義し実装すべきものなのではないだろうか。

そもそも自分自身の認識としてMVCというデザインパターンが前提にあり、ORM = Model(MVCのM)という考え方でORMを捉えており、そこに大きな誤りがあるのでは?と思うようになった。

そもそもORMとは

Prismaの公式ドキュメントとあわせ、ORMについて深掘りしていくと前述の通り自分自身の認識に誤りがある、あるいは偏りがあるということを知ることができた。

そもそものORMとはデータベーススキーマとオブジェクト指向プログラミング言語の間の非互換なデータを変換する(対応付ける)技法であり、そのアプローチは必ずしもデータベーススキーマをModelクラスにマッピングするものと限定されるわけではない。

この部分についてはPrismaの公式ドキュメント中でもWhat are ORMs?という見出しで説明されている。

お恥ずかしながらActive Record = Ruby on RailsのORMという認識しかなかったが、広義のActive RecordはORMの実装パターンの一つであり、Prismaはこの実装パターンではなくData Mapper patternを採用している。
また、上記の説明にもあるがTypeORMではActive RecordとData Mapperの両方のパターンをサポートしているという。

つまり、ORMの選定に際してはアプリケーションに関連する諸々の要件を加味した上で、Active Record patternかData Mapper patternどちらのORMを選ぶかという観点が必要になるのではないだろうか。

違和感から期待感に

実際問題としてActive Record patternのORMからData Mapper patternのORMに移行するには、これまでは深く考えていなかった様々な観点でアプリケーションの設計を構造化する必要があり、手間も増える部分は間違いなくあると思う。

しかしながらそれを補うほどのメリットも多くあると感じる。
特に、再喝となりますが件のドキュメントの冒頭の末尾部分、

This makes the process of querying data a lot more natural as well as more predictable since Prisma Client always returns plain JavaScript objects.

Prisma Clientは常にプレーンなJavaScriptオブジェクトを返すため、データのクエリがより自然で予測可能になる

この部分のメリットをわかりやすく表現するべく、Ruby on Railsにおいては初歩的なパフォーマンスのボトルネックであるN+1について考える。

User.all.each do |user|
  puts user.name
  user.posts.each do |post|
    puts post.title
  end
end

N(SELECT * FROM posts WHERE user_id = {user_id})+1(SELECT * FROM users)のわかりやすい例であり、以下のようにincludesで事前ロードするのがテンプレ的な解消法になるかと。

User.includes(:posts).each do |user|
  puts user.name
  user.posts.each do |post|
    puts post.title
  end
end

こうすると発行されるクエリは、SELECT * FROM usersSELECT * FROM posts WHERE user_id IN ({user_id}, ...)の2クエリとなりレコード数が膨大になってもクエリの実行速度の鈍化が抑えられる。

しかし、実際のアプリケーション開発においてこれほど単純な実装はなく、また実装者のActive Recordに対する理解という点も重要なポイントとなってくる。

そのため、ORMを介したI/Fにおけるクエリの予測性というのは常に意識すべき部分であり、レビューなどの過程において必ず理解度の高いレビュワーのチェックが必要になることが多い。

このケースをPrismaに置き換えてみると以下のようになるが、

const users = await prisma.user.findMany({
  include: {
    posts: true
  }
})

for (const user of users) {
  console.log(user.name)
  for (const post of user.posts) {
    console.log(post.title)
  }
}

findManyの段階で最適化された2クエリ(SELECT * FROM usersSELECT * FROM posts WHERE user_id IN ({user_id}, ...))が発行されるので、N+1は発生しない。

もちろんループ中でのクエリ発行を伴う関数の使用など、Prismaを使用していてもN+1を発生させてしまう可能性はあるが、個人的にはこのData Mapper patternのORMとしての仕様はデータのクエリがより自然で予測可能になるという主張に適切に対応していると感じる。

その他にもData Mapper patternのORMであることによるメリットはいくつもありますが、特にこのクエリが自然で予測可能になるという点は弊社においての課題や今後のプロジェクトにおいて大きなメリットがあるのではないかという期待感を抱けるポイントでした。

おわりに

本記事ではActive Recordとの対比によってData Mapper patternのORMであるPrismaのメリットを示しつつ、自分自身がORMに対する認識を改めるきっかけとなった情報や考察をまとめて記事にしました。

もちろん、Active Record patternのORMではModelクラスの実装により、データベーススキーマに近い距離感でビジネスロジックやリレーションを柔軟かつ簡便に実装できるといった点は相対的にメリットとなり得る部分ではあると思います。

Prismaの公式ドキュメントでは、従来のORM(traditional ORMs)という表現が多く使われていますが、個人的にはこれはアジャイルかウォータフォールかというような対比と同様に適材適所なのではないかと。
そのため、ORMの選定に際してはフラットな視点で必要とされる機能を多角的に検討すべきではないかと思います。

本記事を通して一人でも多くの方にとってORMに対する知見を深めるきっかけとなり、また私のようにPrismaを試してみたけど違和感があるという方の違和感を解消できれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?