generated at
ORMって一体なんなの?
はじめに
関係データベースオブジェクト指向それぞれの視点から、ORMの意味や必要性などについて見ていきます
サンプルのコードにはTypeScriptを用いていますが、その他の言語でも内容については大きく変わりません

関係データベースでは、データベースに存在する任意のテーブル(リレーション)に対して、SQLを使って柔軟に操作を行うことができます。
sql
SELECT id, ordered_at FROM orders;
また、関係データベースでは、データを重複なくきちんと整合性を持って管理するために、正規化を行って、異なるテーブルに分割して管理することが特徴です。
例えば、以下は正規化されていない例です。過去の注文内容が orders という単一のテーブルで管理されています。
orders
idordered_atitem_idquantitypricename
12024-04-0212100商品A
12024-04-0221150商品B
22024-05-0312100商品A
2024-04-02 2024-05-03 という2つの注文データがあります。もし item_id 1 の商品名を変更しようとした場合、 item_id = 1 のすべてのレコードの name を更新する必要があります。
しかし、もしアプリケーションの実装や操作などでミスがあった場合、更新漏れが起きてしまう可能性があります。
これを防ぐためには、テーブルの正規化を行う必要があります。例えば、以下はテーブルの分割例です。(※以下の例では order_items price がまだ重複していますが、実際には注文時点での料金を管理するテーブルを設けて管理した方がよいと思います)
orders
idordered_at
12024-04-02
22024-05-03
order_items
idorder_iditem_idquantityprice
1112100
2121150
3212100
items
idname
1商品A
2商品B
これらの関連する各テーブルは JOIN をすることでまとめて取得することができます
sql
SELECT o.id AS id, o.ordered_at AS ordered_at, i.name AS item_name, oi.quantity * oi.price AS amount FROM orders AS o INNER JOIN order_items AS oi ON o.id = oi.order_id INNER JOIN items AS i ON i.id = oi.item_id WHERE o.id = 1;
まとめると、以下のような特徴があります
特定のテーブルの任意のカラムに対して、SQLを介して自由に抽出や計算などを行うことができる
重複を省き一貫性を維持してデータを管理するため、テーブルを正規化してデータを管理する

先程の関係データベースにおける注文の例を今度はオブジェクト指向の観点から見ていきます。以下は注文を表現する Order クラスです。
typescript
export class Order { readonly id: OrderID; // privateで宣言する (実装の詳細は隠蔽する) // これがたとえ配列ではなくMap<OrderID, OrderItem>などに変わったとしても、外部からは直接操作することはできないので、変更による影響を抑えられます readonly #items: Array<OrderItem>; private constructor(id: OrderID, items: Array<OrderItem>) { this.id = id; this.#items = items; } static create(id: OrderID, items: Array<OrderItem>) { return new Order(id, items); } // CQSに従い、クエリメソッドとコマンドメソッドを分離する... // クエリメソッド sumOfAmount() { // * たとえ#itemsの実装がArray<OrderItem>からMap<OrderId, OrderItem>などに変わったとしても外部には影響がない // * 値の計算が必要な際は、そのデータを管理するオブジェクト自身に任せる // * クエリメソッドには副作用を持たせない return this.#items.reduce((sum, x) => sum.plus(x.amount()), new Money(0)); } // 副作用はコマンドメソッドに隠蔽します // 「契約による設計」という考えにおいては「不変条件」というものがあります // これは、あるオブジェクトが生成されてから破棄されるまでの間、常に満たし続けていなければならない状態のことです // メソッドによってオブジェクトの操作を隠蔽することで、外部からオブジェクトの状態が不用意に更新されてしまうことを防止できます // これにより、不変条件が意図せずして破られてしまうことを防止でき、バグの発生を抑えられます changeQuantity(orderItemID: OrderItemID, newQuantity: number) { const item = this.#items.find((x) => x.id.equals(orderItemID)); assert(item); item.changeQuantity(newQuantity); } }
まず、関係データベースにおけるデータの管理方法との大きな違いとして、注文の明細情報が Order クラスのプロパティによって保持されています。正規化を行い、別々のリレーションで管理する関係データベースとはデータの管理方法が大きく異なります。
typescript
readonly #items: Array<OrderItem>;
また、この items プロパティはプライベートで宣言されており、外部からはアクセスや操作をすることはできません。関係データベースでは任意のテーブルの任意のカラムに対してSQLで柔軟に操作をすることができましたが、逆にオブジェクト指向においては、こういった詳細は外部には隠蔽されます。
その代わり、オブジェクト指向においては、外部にはメソッドを公開し、それを介してのみ操作を許可します。例えば、以下は注文の合計金額を求めるためのメソッドの例です。
typescript
sumOfAmount() { return this.#items.reduce((sum, x) => sum.plus(x.amount()), new Money(0)); }
どうしてこのように詳細を隠蔽し、操作を制限するのでしょうか?以下のようにプロパティを公開して、関係データベースにおけるSQLのようにユーザーに対して自由に操作を許可したほうが、一見は使いやすそうに思えます
typescript
const sumOfAmount = order.items.reduce((sum, x) => sum.plus(new Money(x.price).multiply(x.quantity))), new Money(0));
まず、このように詳細を隠蔽することによるメリットとして、このオブジェクトに変更を行った際の外部への影響を抑えることができるという点が考えられそうです
例えば、 Order クラスにおける明細の管理を Array から Map に変えたとします。
typescript
readonly itemByID: Map<OrderID, OrderItem>;
こうした場合、もし Order クラスの詳細が外部に公開され自由に許可されていたとした場合、 Order クラスの内部で明細が Array で管理されていることに依存していた外部のコード全てに影響が発生してしまいます
例えば、先程の注文の合計金額を求めるコードは以下のように修正する必要があります
typescript
const sumOfAmount = Array.from(order.itemByID.values()).reduce((sum, x) => sum.plus(new Money(x.price).multiply(x.quantity))), new Moeny(0));
以下のように、このような詳細が Order クラスの内部に隠蔽されていれば、このような問題は回避することができます。
typescript
readonly #items: Array<OrderItem>; sumOfAmount() { return this.#items.reduce((sum, x) => sum.plus(x.amount()), new Moeny(0)); }
たとえ注文明細の管理が Array から Map に変わったとしても、その影響は Order クラスの内部のみに留まり、 Order クラスの外部へは影響がありません。
typescript
readonly #itemByID: Map<OrderID, OrderItem>; sumOfAmount() { return Object.values(this.#itemByID).reduce((sum, x) => sum.plus(x.amount()), new Moeny(0)); }
それ以外にも、このように詳細をオブジェクトの内部に隠蔽することには、意図せぬ不整合を防止することができるメリットもあります。
例えば、ある商品の注文数量を変更したいケースがあったとします。
以下は詳細を隠蔽せずにオブジェクトの利用者に対して自由に操作を許可した場合の例です。
typescript
let order: Order; order.items[0].quantity = 150;
最初はこの方法でもうまく動くかもしれません。
しかし、後になって、「ある商品を一度に注文可能な数量は100まで」という制限がアプリケーションに加わったとします。この場合、上記の自由に操作を許可する例の場合、数量を変更している全てのコードをチェックして修正をする必要があります。
契約による設計の考えにおいては、こういったあるオブジェクトが生成されてから破棄されるまでの間に常に満たし続けなければならない状態や条件のことを不変条件と呼びます。オブジェクトの詳細が外部に公開されていることによる問題は、オブジェクトの不変条件を維持することを仕組みとして強制することができないことにあると思います。あるオブジェクトの詳細を外部から隠蔽し、状態に関する操作をそのオブジェクトが持つメソッドのみに限定させることで、不変条件が意図せずして破られることを防止でき、結果としてバグの発生などを防止することができます。
また、この方法には以下のように料金や数量を自由に変更ができてしまうリスクもあります。
typescript
let order: Order; // ... order.items[0].quantity = 50000; order.items[0].price = new Money(1);
もしこういった操作がオブジェクトの内部に隠蔽されていれば、そのオブジェクトの実装のみを修正するだけで済みます。
例えば、以下の場合、 OrderItem クラスの changeQuantity メソッドの内部に変更を加えるだけで対応ができます。
typescript
changeQuantity(orderItemID: OrderItemID, newQuantity: number) { const item = this.#items.find((x) => x.id.equals(orderItemID)); assert(item); item.changeQuantity(newQuantity); }
typescript
export class OrderItem { #quantity: number; #price: Money; changeQuantity(newQuantity: number) { if (newQuantity > 100) { throw new UnacceptableQuantityError(); } this.#quantity = newQuantity; } amount() { return this.#price.multiply(this.#quantity); } }
このように、オブジェクト指向(オブジェクトモデル)においては関係データベース(リレーショナルモデル)とは異なり、外部からの操作などを意図的に制限します。あるデータの操作をそのデータを持つオブジェクト自身にのみ限定させることで、外部への変更の影響を抑えたり、意図せずして不変条件が破られることによる不整合が起きないような仕組みが実現されています。

ORMとは一体何なのか?
上記で紹介したように、オブジェクト指向関係データベースではデータの管理の仕方に大きなギャップがあります (インピーダンスミスマッチ)
例えば、以下のように振る舞いを持たないシンプルなデータ構造(いわゆるDTO)に対してマッピングをしたいだけであれば、ORMの必要性は低いでしょう。
typescript
// データを保持するのみで、特に振る舞いを持たない export interface Order { id: string; orderedAt: Date; } export interface OrderItem { id: string; itemID: string; quantity: number; price: number; itemName: string; }
ライブラリによってはこういった構造のデータをそのまま返してくれるものもあると思います。
typescript
const ordersResult = await db.query('SELECT * FROM orders WHERE id = 1 LIMIT 1'); const order: Order = ordersResult.rows[0]; // データベースドライバーが問い合わせ結果をオブジェクトとしてそのまま返してくれる const orderItemsResult = await db.query(` SELECT oi.id AS id, oi.quantity AS quantity, oi.price AS price, oi.item_id AS itemID, i.name AS itemName FROM order_items AS oi INNER JOIN items AS i ON oi.item_id = i.id WHERE oi.order_id = $1 `, [order.id]); const orderItems: Array<OrderItem> = orderItemsResult.rows;
しかし、オブジェクト指向の方法論にきちんと則って設計しようとすると、関係データベース(リレーショナルモデル)との間でのデータ管理方法のギャップが大きくなり、マッピングがかなり複雑になります
typescript
const ordersResult = await db.query('SELECT * FROM orders WHERE id = 1 LIMIT 1'); const orderItemsResult = await db.query(` SELECT oi.id AS id, oi.quantity AS quantity, oi.price AS price, oi.item_id AS itemID, i.name AS itemName FROM order_items AS oi INNER JOIN items AS i ON oi.item_id = i.id WHERE oi.order_id = $1 `, [orderResult.rows[0].id]); // 複雑なマッピング処理が必要... const order = Order.create( new OrderID(ordersResult.rows[0].id), orderItemsResult.rows.map((x) => { return OrderItem.create( new OrderItemID(x.id), x.quantity, new Money(x.price), Item.create(x.itemID, x.itemName) ); }) );
オブジェクト指向においてはCQS契約による設計など様々な方法論がありますが、そういったものに従えば従うほど関係データベースとのギャップが大きくなり、マッピングが難しくなっていく傾向があります。
ORMはこういった課題などを解消することを目的としています。
ORMが問い合わせ結果をオブジェクトへマッピングしたり、オブジェクトの永続化を簡素化してくれることにより、手間を大きく省くことができます。
typescript
const order = await orm.find(Order, 1); // データベースへ問い合わせを行い、その結果を`Order`オブジェクトにマッピングしてもらう assert(order instanceof Order); order.changeQuantity(orderID, 5); // ORMがオブジェクトへのマッピングの面倒を見てくれるため、利用者は通常通り、オブジェクトの操作に専念できる await orm.save(order); // ORMが永続化に関して面倒を見てくれる
補足
上記にも記述したように、ORMの最大の役割は関係データベースオブジェクト指向におけるギャップ(インピーダンスミスマッチ)を解消することがメインです
以下のような機能は多くのORMが提供しており生産性を上げる上ではとても有用ではありますが、どちらかといえばこういった機能はORMとしては副次的な機能と考えられます
SQLを書かなくてもデータベースを操作できるようにしてくれる
そのため、例えば、以下のようにSQLを直接書くような形であったとしてもORMとしては成り立つと考えられます
typescript
const creds = await orm.find(UserCredentials, `SELECT * FROM user_credentials WHERE user_id = ?`, userID); assert(creds instanceof UserCredentials); creds.regeneratePassword(passwordGenerator); await orm.persist(creds, `UPDATE user_credentials SET ... WHERE user_id = ?`, creds.userID);