DDD(ドメイン駆動設計)の集約においてトランザクションよりも不変条件の方が重要

DDD(ドメイン駆動設計)において、集約はシステムの重要な部分である。多くの場合、集約について話すときにトランザクションが強調されますが、実際には「不変条件」の方が重要である。

不変条件とは

不変条件とは、オブジェクトが常に満たすべき条件や性質のこと。これは「変更不可能なオブジェクト(immutable)」とは異なり、オブジェクトの状態が変わる際にも必ず守られるべきルール(invariant)です。 例えば、銀行口座の残高が負にならないことなどで。

集約と不変条件

集約は、この不変条件を保つように設計されるべきである。TypeScriptを使う場合、zodrefineを使用して不変条件をチェックすることができる。

import { z } from 'zod';

// BankAccountのスキーマを定義
const BankAccountSchema = z.object({
  id: z.string(),
  balance: z.number(),
}).refine(data => data.balance >= 0, {
  message: "Balance must be non-negative",
});

// BankAccount型を定義
type BankAccount = z.infer<typeof BankAccountSchema>;

トランザクションと不変条件

不変条件を常に保つことが約束された集約単位でトランザクションを行えば、不変条件は常に保たれる。

リポジトリの役割

集約の永続化と取得を管理するのがリポジトリ。zodのスキーマを使用してバリデーションを行うことで、不変条件を確実に保つことができる。

class BankAccountRepository {
  private storage: Map<string, BankAccount> = new Map();

  save(account: BankAccount): void {
    // zodのスキーマでバリデーション
    BankAccountSchema.parse(account);
    this.storage.set(account.id, account);
  }

  findById(id: string): BankAccount | undefined {
    return this.storage.get(id);
  }
}

使用例

リポジトリを使って銀行口座を管理する具体例。

const repository = new BankAccountRepository();

const account: BankAccount = { id: "123", balance: 100 };
repository.save(account);

const retrievedAccount = repository.findById("123");
console.log(retrievedAccount); // { id: "123", balance: 100 }

// 不変条件を破る例
const invalidAccount: BankAccount = { id: "456", balance: -50 };
try {
  repository.save(invalidAccount);
} catch (error) {
  console.error(error.errors[0].message); // "Balance must be non-negative"
}

まとめ

不変条件を定義し、永続化を集約単位でのみ行うことでオブジェクトのライフサイクルの間、常に不変条件が保たれるようにすることがDDDにおいて重要である。

参考

Tags: