Understanding Aggregates in DDD
In this article, we'll dive into the concept of aggregates in DDD, using a real-world example: Product Reservation. We’ll also look at implemention of this example using TypeScript.
Aggregate
Haha... simple, graph of relations -- No.
Aggregates help maintain a consistent state by controlling all modifications to the data within their boundaries. When designing an aggregate, we aim to protect the integrity of the domain model, enforce business rules, and maintain transactional boundaries.
Example Scenario: Product Reservation
Let's consider a scenario where we have a Product Reservation system. When a customer wants to reserve a product, we need to ensure that:
- The product is available in stock.
- No other reservation can exceed the available quantity.
- The reservation rules (or policies) are applied consistently.
Defining the Aggregate
To implement this in DDD, we define an aggregate with the following entities:
- Product: The aggregate root that manages the stock.
- Reservation: Represents a request to reserve a product.
Additionally, we will define a ReservationPolicy that encapsulates the rules for making a reservation.
Implementing Aggregates
Let's implement this in TypeScript step-by-step.
Step 1: Define the Entities
First, let's define the Product and Reservation entities.
export class Product {
private readonly id: string;
private availableQuantity: number;
private reservations: Reservation[] = [];
constructor(id: string, availableQuantity: number) {
this.id = id;
this.availableQuantity = availableQuantity;
}
public getId(): string {
return this.id;
}
public getAvailableQuantity(): number {
return this.availableQuantity;
}
public getReservations(): Reservation[] {
return this.reservations;
}
public reserve(quantity: number, policy: ReservationPolicy): void {
policy.validate(this, quantity);
const newReservation = new Reservation(this, quantity);
this.reservations.push(newReservation);
this.availableQuantity -= quantity;
}
}
export class Reservation {
private readonly product: Product;
private readonly quantity: number;
private readonly reservedAt: Date;
constructor(product: Product, quantity: number) {
this.product = product;
this.quantity = quantity;
this.reservedAt = new Date();
}
public getQuantity(): number {
return this.quantity;
}
public getReservedAt(): Date {
return this.reservedAt;
}
}Step 2: Define the Reservation Policy
The ReservationPolicy class will encapsulate the rules to validate a reservation.
import { Product } from './Product';
export class ReservationPolicy {
validate(product: Product, quantity: number): void {
if (quantity <= 0) {
throw new Error("Reservation quantity must be greater than zero.");
}
if (quantity > product.getAvailableQuantity()) {
throw new Error("Not enough stock available for this reservation.");
}
}
}Step 3: Example Usage
Let's see how to use these classes to make a reservation.
import { Product } from './Product';
import { ReservationPolicy } from './ReservationPolicy';
const product = new Product('123', 10);
const reservationPolicy = new ReservationPolicy();
try {
// Attempt to reserve 5 items
product.reserve(5, reservationPolicy);
console.log(`Reserved 5 items. Remaining stock: ${product.getAvailableQuantity()}`);
// Attempt to reserve more items than available
product.reserve(6, reservationPolicy);
} catch (error) {
console.error(error.message);
}Output:
Reserved 5 items. Remaining stock: 5
Not enough stock available for this reservation.Explanation
- Product: This class acts as the aggregate root. It encapsulates the logic for managing reservations and stock quantity.
- Reservation: This entity represents an individual reservation of a product.
- ReservationPolicy: This class contains the business rules required to validate whether a reservation can be made.
By using aggregates, we ensure that all the operations modifying the state of our product are performed consistently, and our domain rules are respected.
Why Use Aggregates?
Aggregates provide several benefits in Domain-Driven Design:
- Consistency: Ensures that all operations on the domain model maintain a consistent state.
- Clarity: By encapsulating related entities and rules within an aggregate, we keep the domain model organized and clear.
- Transactional Boundaries: Aggregates provide a natural boundary for transactions, ensuring all changes within an aggregate are committed or rolled back together.
Conclusion
In this article, we've explored the concept of aggregates in Domain-Driven Design using a TypeScript example of a Product Reservation system. Aggregates play a vital role in maintaining the integrity and consistency of the domain model, especially in complex systems where multiple entities interact.
Next
Visit Factories to learn more.
Check next:
READ
Latest readings
Readings are sites which will help you with detailed
information about given topic. Read latest ones from Learn.
06-03-2026
Build your own local voice assistant powered by Ollama.
06-03-2026
Generate YouTube thumbnails with FastAPI and Ollama.
05-09-2024
Compare Neo4j and Tigergraph databases, which is easier to work with, etc.