GoF以外のプログラミング・デザインパターン #12 Entity / Value Object / Aggregate / Repository

2025/12/03

プログラミング 学習

t f B! P L
eyecatch ドメイン駆動設計(DDD: Domain Driven Design) における基本4パターンを解説したいと思います。 これらは、オブジェクト指向を「現実世界の概念モデル」に近づけるための設計指針なので、ちゃんと理解しておきたいパターンです。 4つのパターンをそれぞれ個別に解説してサンプルコードを掲載しておきます。 それらを組み合わせた関連性について把握して、デザインパターンを理解しましょう。

① Entity(エンティティ)

・識別子(ID)によって区別されるオブジェクト。 ・属性が変わっても「同一のもの」として扱われる。 ・永続化(DB保存)対象であり、ライフサイクルを持つ。

サンプルコード

class User { constructor(id, name, email) { this.id = id; // ← 同一性を保証 this.name = name; this.email = email; } } const u1 = new User(1, "Alice", "a@example.com"); const u2 = new User(1, "Alice", "b@example.com"); console.log(u1.id === u2.id); // true(同一エンティティ)

ポイント解説

・id が同じなら同一人物(状態が変化しても同じオブジェクト)。 ・状態を更新できる(mutable)。

② Value Object(値オブジェクト)

・属性の集合であり、IDではなく値そのもので等価を判定する。 ・不変(immutable)が原則。 ・意味的な単位を明確化するために使う。

サンプルコード

class Money { constructor(amount, currency) { this.amount = amount; this.currency = currency; Object.freeze(this); // ← 不変にする } equals(other) { return this.amount === other.amount && this.currency === other.currency; } } const m1 = new Money(100, "JPY"); const m2 = new Money(100, "JPY"); console.log(m1.equals(m2)); // true(値が同じなら同一)

ポイント解説

・値が等しければ同じオブジェクトと見なす。 ・計算結果として新しいインスタンスを返す(副作用なし)。

③ Aggregate(集約)

・関連するエンティティや値オブジェクトをひとまとめに扱う構造。 ・整合性の単位(トランザクション境界) を定義する。 ・集約の入り口は「集約ルート(Aggregate Root)」と呼ばれる。

サンプルコード

class OrderItem { constructor(product, quantity, price) { this.product = product; this.quantity = quantity; this.price = price; } getTotal() { return this.quantity * this.price; } } class Order { // Aggregate Root constructor(id, customer) { this.id = id; this.customer = customer; this.items = []; } addItem(product, quantity, price) { this.items.push(new OrderItem(product, quantity, price)); } getTotalAmount() { return this.items.reduce((sum, i) => sum + i.getTotal(), 0); } }

ポイント解説

・Order が集約ルート。 ・OrderItem は Order 経由でしか変更できない。 ・データの整合性を保証。

④ Repository(リポジトリ)

・永続化の抽象化層。 ・データストア(DB、API、キャッシュなど)の実装を隠す。 ・アプリケーションは「コレクションのように」ドメインオブジェクトを操作できる。

サンプルコード

class OrderRepository { constructor() { this.orders = new Map(); } save(order) { this.orders.set(order.id, order); } findById(id) { return this.orders.get(id); } findAll() { return Array.from(this.orders.values()); } }

ポイント解説

・ドメイン層からDBアクセスロジックを切り離す。 ・永続化の方式(SQL / NoSQL)を後から変更しても影響が少ない。

関係性(DDDの構成)

[Entity] │↑ unique identity(例: User,Order) │ ├ contains │↑ [Value Object](例: Address,Money) │ └ grouped into │ ↑ [Aggregate](整合性の単位) │ └ accessed through ↑ [Repository](永続化の抽象層)

注意点

EntityとValueObjectを混同すると、整合性管理が破綻しやすいので要注意です。 Aggregateを細かく分けすぎると、トランザクションが複雑になルので、これも設計に気をつける必要があります。 Repositoryはあくまでドメイン層のための抽象化(DAOとは違う)と言う扱いが良いようです。

あとがき

流行りのDDDですが、ドメイン駆動をちゃんと理解しておかないと、設計する時にミスに気がつかない場合もあります。 サンプルコードは、よくあるユーザーの課金オーダーの処理を簡単にコーディングした例ですが、 システム種別によって、さまざまなクラスが作られることがあります。 ただ、4つの工程を理解して、ドメイン駆動が理解して意識できるようになると、安定した設計につながるはずです。 改めて設計って重要!と感じた、デザインパターンの学習でしたね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ