データモデリング
データモデリングとは?
データモデリングという用語は、**アプリケーション内のオブジェクトの形状と構造を定義するプロセス**を指します。これらのオブジェクトはしばしば「アプリケーションモデル」と呼ばれます。リレーショナルデータベース(PostgreSQLなど)では、テーブルに保存されます。ドキュメントデータベース(MongoDBなど)を使用する場合、それらはコレクションに保存されます。
アプリケーションのドメインによって、モデルは異なります。例えば、ブログアプリケーションを作成している場合、ブログ、著者、記事といったモデルがあるかもしれません。カーシェアリングアプリを作成している場合は、おそらくドライバー、車、ルートのようなモデルを持つでしょう。アプリケーションモデルを使用すると、それぞれのデータ構造を作成することで、これらの異なるエンティティをコードで表現できます。
データをモデリングする際、通常以下のような質問をします。
- アプリケーションの主要なエンティティ/概念は何ですか?
- それらは互いにどのように関連していますか?
- それらの主な特徴/プロパティは何ですか?
- それらは自分の技術スタックでどのように表現できますか?
Prisma ORMなしでのデータモデリング
データモデリングは通常、(少なくとも)2つのレベルで行う必要があります。
- **データベース**レベルで
- **アプリケーション**レベルで(つまり、プログラミング言語で)
両方のレベルでアプリケーションモデルが表現される方法は、いくつかの理由により異なる場合があります。
- データベースとプログラミング言語は異なるデータ型を使用する
- リレーションは、データベースとプログラミング言語で異なる方法で表現される
- データベースは通常、インデックス、カスケード削除、またはさまざまな追加の制約(例:ユニーク、NOT NULLなど)のような、より強力なデータモデリング機能を持ちます。
- データベースとプログラミング言語には異なる技術的制約がある
データベースレベルでのデータモデリング
リレーショナルデータベース
リレーショナルデータベースでは、モデルはテーブルによって表現されます。例えば、アプリケーションのユーザーに関する情報を保存するためにusers
テーブルを定義するかもしれません。PostgreSQLを使用する場合、次のように定義します。
CREATE TABLE users (
user_id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255),
email VARCHAR(255) UNIQUE NOT NULL,
isAdmin BOOLEAN NOT NULL DEFAULT false
);
いくつかのランダムなデータを持つusers
テーブルの視覚的な表現は次のようになるでしょう。
user_id | name | email | isAdmin |
---|---|---|---|
1 | Alice | alice@prisma.io | false |
2 | Bob | bob@prisma.io | false |
3 | Sarah | sarah@prisma.io | true |
以下のカラムがあります。
user_id
:users
テーブルに新しいレコードが追加されるたびにインクリメントされる整数です。各レコードのプライマリキーも表します。name
: 最大255文字の文字列です。email
: 最大255文字の文字列です。さらに、追加された制約は、email
カラムに重複する値を持つレコードは存在せず、すべてのレコードがその値を持つ必要があることを示します。isAdmin
: ユーザーが管理者権限を持っているかどうかを示すブール値です(デフォルト値:false
)。
MongoDB
MongoDBデータベースでは、モデルはコレクションによって表現され、任意の構造を持つドキュメントを含みます。
{
_id: '607ee94800bbe41f001fd568',
slug: 'prisma-loves-mongodb',
title: 'Prisma <3 MongoDB',
body: "This is my first post. Isn't MongoDB + Prisma awesome?!"
}
Prisma Clientは現在、一貫性のあるモデルと正規化されたモデル設計を期待します。これは、以下のことを意味します。
- Prismaスキーマにモデルまたはフィールドが存在しない場合、それは無視されます。
- フィールドが必須であってもMongoDBデータセットに存在しない場合、エラーが発生します。
アプリケーションレベルでのデータモデリング
アプリケーションドメインのエンティティを表すテーブルを作成するだけでなく、プログラミング言語でもアプリケーションモデルを作成する必要があります。オブジェクト指向言語では、モデルを表すためにクラスを作成することがよくあります。プログラミング言語によっては、インターフェースや構造体で行う場合もあります。
データベースのテーブルとコードで定義するモデルの間には、しばしば強い相関関係があります。例えば、前述のusers
テーブルのレコードをアプリケーションで表現するには、次のようなJavaScript (ES6) クラスを定義するかもしれません。
class User {
constructor(user_id, name, email, isAdmin) {
this.user_id = user_id
this.name = name
this.email = email
this.isAdmin = isAdmin
}
}
TypeScriptを使用する場合、代わりにインターフェースを定義するかもしれません。
interface User {
user_id: number
name: string
email: string
isAdmin: boolean
}
どちらの場合も、User
モデルが前の例のusers
テーブルと同じプロパティを持っていることに注目してください。データベーステーブルとアプリケーションモデルの間に1:1マッピングがあるのが一般的ですが、モデルがデータベースとアプリケーションで完全に異なる方法で表現されることもあります。
この設定により、users
テーブルからレコードを取得し、それらをUser
型のインスタンスとして保存できます。以下のコードスニペットは、PostgreSQL用のドライバーとしてpg
を使用し、上記で定義されたJavaScriptクラスに基づいてUser
インスタンスを作成します。
const resultRows = await client.query('SELECT * FROM users WHERE user_id = 1')
const userData = resultRows[0]
const user = new User(
userData.user_id,
userData.name,
userData.email,
userData.isAdmin
)
// user = {
// user_id: 1,
// name: "Alice",
// email: "alice@prisma.io",
// isAdmin: false
// }
これらの例では、アプリケーションモデルが「ダム(単純)」であることに注目してください。つまり、ロジックを実装しておらず、その唯一の目的はデータをプレーンなJavaScriptオブジェクトとして運ぶことです。
ORMによるデータモデリング
ORMは、開発者がデータベースを扱いやすくするために、オブジェクト指向言語で一般的に使用されます。ORMの主要な特徴は、基になるデータベースのテーブルにマッピングされるクラスの観点からアプリケーションデータをモデル化できることです。
上記の解説されたアプローチとの主な違いは、これらのクラスがデータを運ぶだけでなく、かなりの量のロジックを実装していることです。主にストレージ、取得、シリアライゼーション、デシリアライゼーションのためですが、時にはアプリケーション固有のビジネスロジックも実装します。
これは、データベースでのデータの読み書きにSQL文を記述するのではなく、モデルクラスのインスタンスがデータを保存および取得するためのAPIを提供するということです。
SequelizeはNode.jsエコシステムで人気のあるORMです。Sequelizeのモデリングアプローチを使用して、前のセクションと同じUser
モデルを定義する方法は次のとおりです。
class User extends Model {}
User.init(
{
user_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: Sequelize.STRING(255),
email: {
type: Sequelize.STRING(255),
unique: true,
},
isAdmin: Sequelize.BOOLEAN,
},
{ sequelize, modelName: 'user' }
)
このUser
クラスの例を機能させるには、データベースに対応するテーブルを作成する必要があります。Sequelizeには、これを行う2つの方法があります。
User.sync()
を実行する(通常、本番環境では推奨されません)- データベーススキーマを変更するためにSequelizeマイグレーションを使用する
前のセクションで示されたように、User
クラスを手動でインスタンス化する(new User(...)
を使用する)ことはなく、代わりにUser
クラスの静的メソッドを呼び出し、それがUser
モデルインスタンスを返すことに注意してください。
const user = await User.findByPk(42)
findByPk
の呼び出しは、ID値42
で識別されるUser
レコードを取得するためのSQLステートメントを作成します。
結果として得られるuser
オブジェクトは、SequelizeのModel
クラスのインスタンスです(User
がModel
を継承しているため)。POJO(プレーンなJavaScriptオブジェクト)ではなく、Sequelizeの追加動作を実装するオブジェクトです。
Prisma ORMによるデータモデリング
アプリケーションでPrisma ORMのどの部分を使用したいかによって、データモデリングのフローは若干異なります。次の2つのセクションでは、Prisma Clientのみを使用する場合と、Prisma ClientとPrisma Migrateを使用する場合のワークフローについて説明します。
しかし、どのアプローチを使用するにしても、Prisma ORMでは、プログラミング言語でクラス、インターフェース、または構造体を手動で定義してアプリケーションモデルを作成することはありません。代わりに、アプリケーションモデルはPrismaスキーマで定義されます。
- Prisma Clientのみ:Prismaスキーマのアプリケーションモデルは、データベーススキーマのイントロスペクションに基づいて生成されます。データモデリングは主にデータベースレベルで行われます。
- Prisma ClientとPrisma Migrate:データモデリングは、Prismaスキーマにアプリケーションモデルを手動で追加することによって行われます。Prisma Migrateは、これらのアプリケーションモデルを基になるデータベースのテーブルにマッピングします(現在はリレーショナルデータベースのみサポートされています)。
例として、前の例のUser
モデルはPrismaスキーマで次のように表現されます。
model User {
user_id Int @id @default(autoincrement())
name String?
email String @unique
isAdmin Boolean @default(false)
}
アプリケーションモデルがPrismaスキーマにある(イントロスペクションによって追加されたか、手動で追加されたかに関わらず)場合、次のステップは通常、Prisma Clientを生成することです。これにより、アプリケーションモデルの形でデータを読み書きするためのプログラム的で型安全なAPIが提供されます。
Prisma Clientは、コードでアプリケーションモデルを表現するためにTypeScriptの型エイリアスを使用します。例えば、User
モデルは生成されたPrisma Clientライブラリで次のように表現されます。
export type User = {
id: number
name: string | null
email: string
isAdmin: boolean
}
生成された型に加えて、Prisma Clientは@prisma/client
パッケージをインストールすると使用できるデータアクセスAPIも提供します。
import { PrismaClient } from '@prisma/client'
// or
// const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
// use inside an `async` function to `await` the result
await prisma.user.findUnique(...)
await prisma.user.findMany(...)
await prisma.user.create(...)
await prisma.user.update(...)
await prisma.user.delete(...)
await prisma.user.upsert(...)
Prisma Clientのみを使用する
アプリケーションでPrisma Clientのみを使用し、Prisma Migrateを利用しない場合、データモデリングはSQLを介してデータベースレベルで行う必要があります。SQLスキーマの準備ができたら、Prismaのイントロスペクション機能を使用してアプリケーションモデルをPrismaスキーマに追加します。最後に、Prisma Clientを生成すると、データベースのデータを読み書きするための型とプログラムAPIが作成されます。
主なワークフローの概要は次のとおりです。
- SQLを使用してデータベーススキーマを変更する(例:
CREATE TABLE
、ALTER TABLE
など) prisma db pull
を実行してデータベースをイントロスペクトし、アプリケーションモデルをPrismaスキーマに追加するprisma generate
を実行してPrisma Client APIを更新する
Prisma ClientとPrisma Migrateを使用する
Prisma Migrateを使用する場合、アプリケーションモデルをPrismaスキーマで定義し、リレーショナルデータベースではprisma migrate
サブコマンドを使用してプレーンなSQLマイグレーションファイルを生成します。これらのファイルは適用する前に編集できます。MongoDBでは、代わりにprisma db push
を使用し、変更をデータベースに直接適用します。
主なワークフローの概要は次のとおりです。
- Prismaスキーマ内のアプリケーションモデルを手動で変更する(例:新しいモデルの追加、既存のモデルの削除など)
prisma migrate dev
を実行してマイグレーションを作成および適用するか、prisma db push
を実行して変更を直接適用します(どちらの場合もPrisma Clientは自動的に生成されます)