ORMって一体なんなの?
code:typescript
class Order {
readonly id: OrderID;
// privateで宣言する (実装の詳細は極力隠蔽する)
// これがたとえ配列ではなくMap<OrderID, OrderItem>などに変わったとしても、外部からは直接操作することはできないので、変更による影響を抑えられます
// コンストラクタがprivateになっている
private constructor(id: OrderID, items: Array<OrderItem>) {
this.id = id;
this.#items = items;
}
// インスタンスを生成する際はstaticメソッドを経由させたい
static create(id: OrderID, items: Array<OrderItem>) {
return new Order(id, items);
}
static createFromXXX(...) {
// ...
}
// CQSに従い、クエリメソッドとコマンドメソッドを分離する...
// クエリメソッド
sumOfAmount() {
// * たとえ#itemsの実装がArray<OrderItem>からMap<OrderId, OrderItem>などに変わったとしても外部には影響がない
// * 値の計算が必要な際は、そのデータを管理するオブジェクト自身に任せる
// * クエリメソッドには副作用を持たせない
return this.#items.reduce((x, y) => x + y, 0);
}
// 副作用はコマンドメソッドに隠蔽します
// 「契約による設計」という考えにおいては「不変条件」というものがあります
// これは、あるオブジェクトが生成されてから破棄されるまでの間、常に満たし続けていなければならない状態のことです
// メソッドによってオブジェクトの操作を隠蔽することで、外部からオブジェクトの状態が不用意に更新されてしまうことを防止できます
// これにより、不変条件が意図せずして破られてしまうことを防止でき、バグの発生を抑えられます
// TODO: ちゃんとした例にする
doSomethingXXX(...) {
...
}
}
こういったオブジェクトをデータベースへ永続化または復元しようとすると、マッピングが複雑になり手間がかかる
オブジェクトの内部実装を隠蔽し、副作用はコマンドメソッド(CQS)のみを介してのみ許可するなど、オブジェクト指向の方法論に則ってきちんと設計しようとすればするほどリレーショナルモデルとのギャップがより大きくなっていき、マッピングがどんどん複雑になっていきます 特に振る舞いを持たない単純なDTOとリレーションをマッピングするだけであれば、大抵そこまで手間はかからない ORMはこのあたりの課題などを解消することを目的としています ※SQLを書かなくてもデータベースを操作できるようにしてくれる機能などは多くのORMが提供しており、生産性を上げる上ではとても有用ではありますが、どちらかといえばそういった機能はORMとしては副次的な機能にあたります そのため、例えば、以下のようにSQLを直接書くような形であったとしてもORMとしては成り立つと考えられます code: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);
マッピングの定義について