集約を使ってドメインのルールを守る
ドメインのルール
ドメインの題材として、KTSLAB.高校の部活動を考えてみます。
KTSLAB.高校の部活動には以下のようなルールが存在します。
- 部活動として成り立つためには以下の条件を満たす必要がある
- 最低でも3人の部員が所属している
例えば、学生が2人しかいない部活動は存在しません。
集約を使えば、このルールを常に守ることができます。
集約を切ってみる
まずは上記のルール中に出てきた、部活動(Club)、部員(Member)の関連を図におこしてみましょう。
部活動は、所属する部員数の変化があれば、都度ルールを満たしているかをチェックする必要があります。
つまり、部活動が検知できない部員数の変化が起きてはいけないということです。
もし、部活動が検知できないところで部員数の変化があると、部活動は承認状態を変更することができません。こうなると、本来は未承認であるのに承認のままとなっている部活動が存在することになってしまいます。
集約を使えば、この問題を解決することができます。
具体的には、以下のように部活動をルートとした集約を作成します。
集約を作成すると、集約内のオブジェクトを直接更新できるのは集約ルートに限定されます。今回の例でいうと、部員の数を減らすという操作は部活動にしか許されていないということです。
別の視点で捉えると、外部から集約内のオブジェクトを変更したいときは必ず集約ルートに変更依頼をする必要がある、ということになります。
コードにしてみる
先ほどの例を具体的なコードで表現してみましょう。
まずは、集約が守られていない疑似コードです。
class Club {
public List<Member> members;
// 承認状態を確認する
public String checkApproval() {
if (members.size() < 3) {
return "未承認";
} else {
return "承認済";
}
}
// 他は省略
}
final var club = new Club();
club.members.add(new Member("田中")); // メンバー追加時に承認状態が確認されていない
承認状態を確認するロジック(checkApproval()
)はClub
に書かれていますが、クライアントコードで直接所属する部員を操作できてしまうので、部員追加時に常にcheckApproval()
が呼ばれることが保証できていません。
次に集約が守られている疑似コードを見てみましょう。
class Club {
private List<Member> members; // 外部からのアクセスを制限
// メンバー追加時に承認状態を確認するわね。
public String addMember(Member member) {
members.add(member);
return checkApproval();
}
// 承認状態を確認する
private String checkApproval() {
if (members.size() < 3) {
return "未承認";
} else {
return "承認済";
}
}
}
final var club = new Club();
club.addMember(new Member("田中")); // メンバー追加時に承認状態が必ず確認される
members
の公開範囲を狭めることで、クライアントコードから直接所属する部員を操作することができなくなっています。
クライアントコードに公開されているのは、addMember()
だけになっており、addMember()
では必ず承認状態を確認するようになっているので、実態と異なる承認状態を持つ部活動が存在することはなくなりました。
改めてコードを眺めてみると、外部から部活動集約内のオブジェクトである部員を操作するには部活動に依頼する必要がある、という関係が守られていることが理解できると思います。
結論、集約ってなに
集約についてのまとめを以下に示します。
- 必ず守りたい整合性を持ったオブジェクトのまとまり
- 今回だと、「3人以上の部員を持つ部活動は承認されている」というドメインのルールが守られているまとまり
- 集約内のオブジェクトを直接操作できる権限を持つのが集約ルートに限定されていることが重要
- 今回だと、部員を操作したいときには部活動に依頼する必要があった
Discussion