AggregateRoot – 集約

  • 2018.07.31
  • DDD
AggregateRoot – 集約

概要

ドメイン駆動設計のモデリングの要素の一つ、AggregateRoot(AR, 集約)の解説です。

DDD 関連記事リンク

◆ ValueObject
記事リンク: https://nrslib.com/valueobject/
◆ Entity
記事リンク: https://nrslib.com/entity/
◆ AggregateRoot(イマココ)
記事リンク: https://nrslib.com/aggregateroot/
◆ Repository
記事リンク: https://nrslib.com/repository/

解説

Aggregate Root

AggregateRoot は日本語で集約といいます。

集約は何を表し、何故集約という概念が必要なのでしょうか。

トランザクション整合性などで説明をされている記事はすでに多く存在するので、この記事ではオブジェクト指向の観点から考察します。

デメテルの法則

デメテルの法則というのはご存知でしょうか。

「直接インスタンス化したもの」か「引数として渡されたもの」以外のものを使ってはいけないという法則ですね。

例えば以下のコードを見てみましょう。
dog から Body を取得して body.Run のメソッドを呼んでいます。
「直接インスタンス化したもの」ではなく「引数として渡されたもの」でもありませんね(引数として渡されたもののプロパティに対して命令している)。
プログラムとしては正しく動作しますが、デメテルの法則の観点からすると間違っています。

デメテルの法則を抜きにしたとしても、犬から身体を取得してその身体に対して走れという命令するのは果たして素直なプログラムなのでしょうか。

もちろんプログラムを現実のモデリングに合わせる必要は全くありません。
しかし抽象化の概念から考えたとしても、Program.Run メソッドを見たときに「Dog から Body を取得してその Body の Run というメソッドを呼ぶ」という処理はあまりに具体的すぎます。

この具体性から発生するデメリットを感じるために、例えば Foots というクラスを作りとして Dog に実装してみましょう。Foots にはもちろん Run というメソッドがあります。
変更後のプログラムを確認すると Program.Run メソッドにも変更が波及しています。
Dog クラスに対する変更なのに Dog が使われている箇所すべてを確認する必要があるでしょう。

この問題を防ぐためには Dog クラスに Run というメソッドを持たせ、そのメソッドを呼び出すようにします。
Program.Run メソッドではもはや Dog の Body を取得する必要はなくなり、Dog に対して走れと命令するだけになりました。
現実のモデリングの観点や抽象化の観点から考えて、より良くなっているように思えます。

Foots クラスを作った場合の修正も Body が公開されていないので Dog クラスの内部だけを修正すればよいことになります。
結果として修正箇所がこのクラス内に収まるようになります。

次にもう少し実践的なクラスを題材にしてみます。

ブログシステムを考えてみましょう。
記事のモデルがあり、ユーザーのモデルが存在します。
Article (記事) クラスと User クラスをモデリングしてみました。
ドメインモデル貧血症と呼ばれる状態になっているモデルです。

デメテルの法則の観点から見たときのこのモデルの最も大きな問題は、以下のようなコードが書けてしまうことです。
記事から著者を取得して、その著者の名前を変更するということができてしまいます。
これが正しい処理とした場合 User クラスを変更する場合は毎回 Article クラス経由で変更を行うのでしょうか。
少し違和感を感じますね。

これを防ぐには以下のようにコードを書き換えます。
もはや Article は Auther プロパティを提供しません。
そのため先ほどのように Article から User を取得してその名前を変更するといういびつな処理を書くことができなくなりました。

この Article は id を持っているので、同一性という特徴を持ちます。よって以前の記事でご紹介したエンティティ(Entity)です。
そして、エンティティであると同時に集約(AggregateRoot)でもあるのです。

集約とは何か

エンティティの中でも直接操作するエンティティが集約です。

集約の内部に保持するエンティティを操作する場合は、集約のメソッドを呼び、集約がエンティティに対して処理を行います。
つまり集約はメンバー変数のエンティティを操作できます。

こうすることによりエンティティから getter でインスタンスを取得し、そのメソッドを呼ぶようなことが起きなくなります。
つまり自然とデメテルの法則が守られることになります。

例えば先ほどの例の Article クラスから User の名前を変更する場合、この集約のルールに従って記述すると、次のようになります。
Article に ChangeUserName というメソッドが記述されることの歪さが際立つのではないでしょうか。
getter ではあまり感じることができない違和感でも、メソッドにすることによって大きな違和感になります。

この場合は素直に User を取得して、User を直接操作する方がよいでしょう。

ここで気づいた方もいるかと思いますが User もまた集約なのです。

つまり、集約はエンティティを保持することができ、その保持されたエンティティもまた集約であることがあるのです。

メソッドの違和感

Article に ChangeUserName というメソッドがあるのはとても違和感があります。
では User に ChangeName というメソッドがあるのはどうでしょうか。
とても自然に思えます。

違和感を感じるか、感じないかに従ってモデルにメソッドを定義していくと、自然と不必要なメソッドが定義されなくなり、定義されるべきクラスにメソッドが定義されるようになります。

この作業を繰り返すと必要なメソッドだけがクラスに定義されていきます。まるで蒸留されたかのように、とても凝集度が高いクラスです。
その出来上がったクラスがドメインモデルと呼ばれるものです。

まとめ

集約はエンティティです。
集約の内部のエンティティを直接操作しないようにします。
集約は保持するエンティティを操作してかまいません。
集約の内部のエンティティを公開しないことにより、歪な操作を避けることができます。

getter を使わずメソッドを呼び出し、そのインスタンスにフィールドを操作させるというのはオブジェクト指向では当たり前の書き方です。

その当たり前の書き方を繰り返すことで凝集度が高いクラスになります。

DDDカテゴリの最新記事