メインコンテンツにスキップ

データモデリング

データモデリングとは?

データモデリングという用語は、アプリケーション内のオブジェクトの形状と構造を定義するプロセスを指します。これらのオブジェクトはしばしば「アプリケーションモデル」と呼ばれます。リレーショナルデータベース(PostgreSQLなど)では、これらはテーブルに格納されます。ドキュメントデータベース(MongoDBなど)を使用する場合、これらはコレクションに格納されます。

アプリケーションのドメインに応じて、モデルは異なります。たとえば、ブログアプリケーションを作成している場合、ブログ著者記事などのモデルがあるかもしれません。カーシェアリングアプリを作成している場合、ドライバールートのようなモデルがあるでしょう。アプリケーションモデルを使用すると、それぞれのデータ構造を作成することにより、これらの異なるエンティティをコード内で表現できます。

データモデリングを行う際、通常は次のような質問をします。

  • アプリケーションの主なエンティティ/概念は何ですか?
  • それらは互いにどのように関連していますか?
  • それらの主な特性/プロパティは何ですか?
  • それらは私の技術スタックでどのように表現できますか?

Prisma ORMなしのデータモデリング

データモデリングは通常、(少なくとも)2つのレベルで行う必要があります。

  • データベースレベルで
  • アプリケーションレベルで(つまり、プログラミング言語で)

アプリケーションモデルが両方のレベルで表現される方法が異なる理由はいくつかあります。

  • データベースとプログラミング言語は異なるデータ型を使用します
  • リレーションは、データベースとプログラミング言語で異なる方法で表現されます
  • データベースは通常、インデックス、カスケード削除、またはさまざまな追加制約(例:ユニーク、非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_idnameemailisAdmin
1Alicealice@prisma.iofalse
2Bobbob@prisma.iofalse
3Sarahsarah@prisma.iotrue

これには次の列があります。

  • 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
// }

これらの例では、アプリケーションモデルは「ダム」であることに注意してください。つまり、ロジックを実装していませんが、その唯一の目的は、データをPlain Old JavaScript Objectsとして運ぶことです。

ORMを使用したデータモデリング

ORMは、開発者がデータベースを扱いやすくするために、オブジェクト指向言語で一般的に使用されています。ORMの重要な特徴は、アプリケーションデータを、基盤となるデータベースのテーブルにマッピングされるクラスの観点からモデル化できることです。

上記の approach との主な違いは、これらのクラスがデータだけでなく、実質的な量のロジックも実装していることです。主にストレージ、取得、シリアライズ、およびデシリアライズのためですが、アプリケーションに固有のビジネスロジックを実装することもあります。

これは、データベースでデータを読み書きするためにSQLステートメントを書くのではなく、モデルクラスのインスタンスがデータを保存および取得するためのAPIを提供することを意味します。

SequelizeはNode.jsエコシステムで人気のあるORMです。これは、Sequelizeのモデリング approach を使用して、前のセクションと同じ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 migrationsを使用してデータベーススキーマを変更する

前のセクションで示したように、Userクラスを手動でインスタンス化する(new User(...)を使用)ことは決してなく、代わりにUserクラスでstaticメソッドを呼び出すと、Userモデルインスタンスが返されることに注意してください。

const user = await User.findByPk(42)

findByPkの呼び出しは、ID値42で識別されるUserレコードを取得するためのSQLステートメントを作成します。

結果として得られるuserオブジェクトは、SequelizeのModelクラスのインスタンスです(UserModelを継承するため)。これはPOJOではなく、Sequelizeからの追加の動作を実装するオブジェクトです。

Prisma ORMを使用したデータモデリング

アプリケーションで使用したいPrisma ORMの parts に応じて、データモデリングフローは少し異なります。次の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を生成することです。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が作成されます。

主なワークフローの概要は次のとおりです。

  1. SQLを使用してデータベーススキーマを変更します(例:CREATE TABLEALTER TABLEなど)
  2. prisma db pullを実行してデータベースをイントロスペクトし、アプリケーションモデルをPrismaスキーマに追加します
  3. prisma generateを実行してPrisma Client APIを更新します

Prisma ClientとPrisma Migrateを使用する

Prisma Migrateを使用する場合、Prismaスキーマでアプリケーションモデルを定義し、リレーショナルデータベースではprisma migrateサブコマンドを使用して、プレーンなSQLマイグレーションファイルを生成します。これらは、適用する前に編集できます。MongoDBでは、代わりにprisma db pushを使用して、変更をデータベースに直接適用します。

主なワークフローの概要は次のとおりです。

  1. Prismaスキーマでアプリケーションモデルを手動で変更します(例:新しいモデルを追加する、既存のモデルを削除するなど)
  2. prisma migrate devを実行してマイグレーションを作成および適用するか、prisma db pushを実行して変更を直接適用します(どちらの場合もPrisma Clientは自動的に生成されます)