はじめに
DDDを学んでいく中でアグリゲートというワード。なかなか理解できないので、調べながらわかった事を記事にしていきます。
アグリゲートとは
ドメイン駆動設計(DDD)の中心的な概念の一つであり、データの整合性を保ちながらドメインの複雑さを管理するための方法です。アグリゲートは、関連するエンティティと値オブジェクトを一つの単位としてまとめ、それらの間の操作を統制します。ここで重要なのは、アグリゲート全体として一貫性を保つことです。
アグリゲートのポイント
アグリゲートルート (Aggregate Root):
アグリゲートの入り口となるエンティティ。外部から直接操作できる唯一のエンティティです。アグリゲート全体の整合性を保証する責任を持っています。
エンティティ (Entity):
一意の識別子を持ち、アグリゲートルートに従属するエンティティ。
アグリゲート内でのみ操作され、外部から直接操作されることはありません。
値オブジェクト (Value Object):
識別子を持たず、属性によって識別される不変のオブジェクト。
状態を持たないため、操作されることはなく、アグリゲートルートやエンティティに属します。
具体例:注文 (Order) アグリゲート
値オブジェクトの定義
class Address {
constructor(
private street: string,
private city: string,
private zipCode: string
) {}
get fullAddress() {
return `${this.street}, ${this.city}, ${this.zipCode}`;
}
}
エンティティの定義
class OrderItem {
constructor(
private productId: string,
private quantity: number,
private price: number
) {}
get total() {
return this.quantity * this.price;
}
get details() {
return {
productId: this.productId,
quantity: this.quantity,
price: this.price,
};
}
}
アグリゲートルートの定義
class Order {
private items: OrderItem[] = [];
constructor(
private id: string,
private customerId: string,
private shippingAddress: Address
) {}
addItem(productId: string, quantity: number, price: number) {
const item = new OrderItem(productId, quantity, price);
this.items.push(item);
}
removeItem(productId: string) {
this.items = this.items.filter(item => item.details.productId !== productId);
}
changeShippingAddress(newAddress: Address) {
this.shippingAddress = newAddress;
}
get orderDetails() {
return {
id: this.id,
customerId: this.customerId,
shippingAddress: this.shippingAddress.fullAddress,
items: this.items.map(item => item.details),
total: this.items.reduce((sum, item) => sum + item.total, 0),
};
}
}
リポジトリの定義
interface OrderRepository {
findById(id: string): Order | null;
save(order: Order): void;
}
class InMemoryOrderRepository implements OrderRepository {
private orders: Map<string, Order> = new Map();
findById(id: string): Order | null {
return this.orders.get(id) || null;
}
save(order: Order): void {
this.orders.set(order.orderDetails.id, order);
}
}
使用例
const address = new Address('123 Main St', 'Anytown', '12345');
const order = new Order('order-1', 'customer-1', address);
order.addItem('product-1', 2, 100);
order.addItem('product-2', 1, 200);
const orderRepository = new InMemoryOrderRepository();
orderRepository.save(order);
const savedOrder = orderRepository.findById('order-1');
if (savedOrder) {
console.log(savedOrder.orderDetails);
// Output: Order details with items and total
}
アグリゲートの重要性
一貫性の維持: アグリゲートは、一貫性を保つための単位です。アグリゲート内の全てのエンティティと値オブジェクトの整合性を保証します。
責任の集中: アグリゲートルートが一貫性と操作の責任を持つことで、コードの分かりやすさと保守性が向上します。
外部からの制御: アグリゲートルートを通じてのみ操作が行われるため、データの不整合を防ぎます。