はじめに
この記事は、以下の記事の補足説明です。
集約ルートの指定と型制約
エンティティが、別の集約ルートに属するエンティティとコンポジションを構成してはならない。例えば以下のような注文(集約ルート)と注文明細があった場合、
@Entity public class Order { @OneToMany private List<LineItem> items; // ... } @Entity public class LineItem { // ... }
外部の集約ルートである顧客エンティティが注文明細の参照を持ってはならない。
@Entity public class Customer { @ManyToOne private LineItem lineItem; }
これを防ぐために、jmolecules では AggregateRoot
と Entity
インターフェースに以下のような型引数が設けられる。
public interface Entity<T extends AggregateRoot<T, ?>, ID> extends Identifiable<ID> { }
public interface AggregateRoot<T extends AggregateRoot<T, ID>, ID extends Identifier> extends Entity<T, ID> { }
これにより、LineItem
の属する集約ルートは、(AggregateRoot
である)Order
であることを明示することができる(AggregateRoot
ではないエンティティは集約ルートとして指定できない型制約にもなっている)。
public class Order implements AggregateRoot<Order, OrderId> { ... } public class LineItem implements Entity<Order, Long> { ... }
以下のような Customer
を定義した場合は、単体テストなどでリフレクションを使用することで他の集約ルート配下のエンティティを参照している場合にテストを失敗とすることが可能となる。
public class Customer implements AggregateRoot<Customer, CustomerId> { private LineItem lineItem; }
リポジトリに対する制約
DDD では、集約ルートに対してリポジトリを提供し、その他のエンティティに対してリポジトリを提供しない。
jmolecules では以下のリポジトリインターフェースを提供しており、対象のエンティティが AggregateRoot
であることを型制約として課している。
public interface Repository<T extends AggregateRoot<T, ID>, ID extends Identifier> { }
外部の集約ルート参照に対する制約
Order
と Customer
がいずれも集約ルートであった場合、以下のように Order
が Customer
の参照を持つべきではない。
@Entity public class Order { @Id private Long id; @OneToMany private List<LineItem> lineItems; @ManyToOne private Customer customer; }
外部の集約ルートを参照する場合、以下のようなバリューオブジェクトを用意し
@Embeddable public class CustomerId { @Column(name = "customer_id") private Long customerId; }
以下のように CustomerId
を保持することが推奨される。
@Entity public class Order { @Id private Long id; @OneToMany private List<LineItem> lineItems; @Embedded private CustomerId customerId; }
jmolecules の AggregateRoot
では、ID値は ID extends Identifier
のようになっており、Identifier
の実装であることが課せられる(Identifier
単純なマーカーインターフェース)。
public interface AggregateRoot<T extends AggregateRoot<T, ID>, ID extends Identifier> extends Entity<T, ID> { }
集約ルートの Customer
は以下のような定義となり、
public class Customer implements AggregateRoot<Customer, CustomerId> { @Embedded @Id private CustomerId id; // ... } @Embeddable public record CustomerId(UUID id) implements Identifier {}
Order
側では CustomerId
をフィールド値として保持する。
public class Order implements AggregateRoot<Order, OrderId> { // ... @Embedded private CustomerId customer; // ... }
直接 Customer
の参照を持つことを防ぐには、こちらも単体テストなどでリフレクションを使い、フィールドの型が AggregateRoot
のサブタイプの場合にテストを失敗とすることが可能となる。
外部の集約ルートの参照には Association
を使うことができる。Association
については以下を参照。