DDDに関して気付いた事を供養する場所
エンティティを定義する時どうしてもRDBのテーブル単位で見てしまう。
そのコンテキストではどの概念を扱うのか、そのエンティティはどの要素から構成されているのかを常に意識する事。
オブジェクトはイミュータブルでさえあればいいか〜
と浅はかに考えていたが、フィールドの公開範囲を考えずにgetterを設定すると容易にデメテルの法則を破るコードが生まれてしまので注意したい。
戒めとしてざっと記事を書いた
境界付けられたコンテキスト
と言う概念自体はDDDの文脈で出て来たものだと認識している。
IDDDでは
境界付けられたコンテキスト : コアドメイン = 1 : 1
と記述されている。
しかし、似た概念でモジュールというものも紹介されている。クリーンアーキテクチャではgemやjarファイルといったそれ単体でデプロイ可能な単位と紹介されていた気がする。
なので自分は境界づけられたコンテキスト
≒ モジュール
くらいの認識でいる。
基底クラスの議論があまりないのが気になる。
例えばエンティティだったら下記のような基底クラスを作成して運用するがいかがだろうか?
下記の例は一番シンプルな例で、id
をstringではなく固有のEntityIdentifier
のようなバリューオブジェクトで定義している例もある。
abstract class Entity<T> {
// idがRDBのautoincrementを使用していない場合
// uuidなどで識別子の早期生成を採用している場合を想定している
abstract id: string;
public equals(that: Entity<T>): boolean {
return this.id === that.id;
}
}
では集約は?と聞かれたら自分は下記のような基底クラスを書く。
interface DomainEvent {
eventVersion: number;
occurredOn: Date;
}
abstract class AggregateRoot<T> extends Entity {
abstract domainEvents: DomainEvent[];
public occur(domainEvent: DomainEvent): void {
this.domainEvents.push(domainEvent);
}
public publish(domainEvent: DomainEvent): void {
// DomainEventSubcriberContainerは発行された
// ドメインイベントをサブスクライブしているクラスを引っ張ってくる
// DIコンテナ的なものをイメージして頂けると良いと思う
DomainEventSubcriberContainer.handle(domainEvent);
}
public publishAll(): void {
this.domainEvents.forEach(domainEvent => {
this.publish(domainEvent);
});
}
}
エンティティとの違いはドメインイベントを保持しているかどうかだという認識。IDDDの通り、集約は他の集約を識別子のみで参照する。他のコンテキストとの通信は全てドメインイベント経由で行い、発行した後のことは集約自身は知らない。トランザクションについてもドメインイベントの発行による結果整合性で担保する。結果整合性を許容できない場合は集約の境界が間違っている可能性が高いので集約の設計を見直す。
ドメインイベントを発行するレイヤーだが
- インフラ層
- ドメイン層
- ユースケース層
色々な層での発行例が見られるが、個人的にはインフラ層で発行することによってDBに正常に値が書き込まれた時にのみドメインイベントを発行する事ができるようになるのでインフラ層が有力な気がしている。
ドメイン イベント: 設計と実装
MSのドキュメントでもインフラ層での発行が紹介されている。永続化層をSingle Source of Truth
として設計しましょうという事なのかな?