DDD Factories
Factory
In Domain-Driven Design (DDD), a Factory is a design pattern used to create complex objects. Factories are useful when object creation involves significant complexity or when building the object requires ensuring that certain invariants are upheld. Factories allow for the encapsulation of this creation logic, keeping the responsibility for object creation separate from other parts of the system.
Factories are particularly useful when:
- The object creation process is complex (e.g., constructing aggregates with multiple entities).
- The object has invariants or validation logic that must be satisfied during creation.
- You want to hide the details of how an object is created from the rest of the application.
Key Characteristics
-
Encapsulation of Creation Logic: Factories centralize the logic required to create objects, especially when it involves multiple steps or dependencies.
-
Simplification of Client Code: By using a factory, client code can focus on higher-level tasks, avoiding the complexities involved in creating objects.
-
Aggregate Creation: In DDD, factories are often used to create aggregates (root entities along with their related entities), ensuring that all the invariants for the aggregate are enforced when it is constructed.
Example
Let’s continue with our example from e-commerce, where we have an Order entity. Creating an Order might involve a series of steps such as validating the customer, checking inventory for products, and adding items to the order. A factory can encapsulate all this logic.
1. Defining a Factory for Creating Orders
We’ll define a factory class that is responsible for creating an Order aggregate. The factory will ensure that:
- The
Orderis created with a valid customer. - The items added to the order are in stock.
class OrderItem {
constructor(public productId: string, public quantity: number, public price: number) {}
}
class Order {
private items: OrderItem[] = [];
private status: 'Pending' | 'Shipped' | 'Delivered' = 'Pending';
constructor(public orderId: string, public customerId: string, public orderDate: Date) {}
addItem(item: OrderItem) {
this.items.push(item);
}
getTotalAmount(): number {
return this.items.reduce((total, item) => total + item.quantity * item.price, 0);
}
}
class Customer {
constructor(public customerId: string, public name: string) {}
}
class Product {
constructor(public productId: string, public price: number, public stock: number) {}
decreaseStock(quantity: number) {
if (this.stock < quantity) {
throw new Error('Not enough stock for product ' + this.productId);
}
this.stock -= quantity;
}
}
class OrderFactory {
createOrder(customer: Customer, products: Product[], quantities: number[]): Order {
if (products.length !== quantities.length) {
throw new Error('Products and quantities must match');
}
const order = new Order(this.generateOrderId(), customer.customerId, new Date());
products.forEach((product, index) => {
const quantity = quantities[index];
product.decreaseStock(quantity);
order.addItem(new OrderItem(product.productId, quantity, product.price));
});
return order;
}
private generateOrderId(): string {
return 'order_' + Math.random().toString(36).substr(2, 9);
}
}Explanation
- The OrderFactory encapsulates the process of creating an
OrderwithOrderItems. It ensures that each product has enough stock before adding it to the order. - The createOrder method accepts a
Customer, a list ofProducts, and their corresponding quantities. - The factory handles all the logic for creating the
Orderand ensures that theOrderis correctly initialized with valid data.
2. Usage of the Factory
Let’s see how the factory can be used to create an order:
const customer = new Customer('customer123', 'Alice');
const product1 = new Product('product1', 50, 10); // 10 units in stock
const product2 = new Product('product2', 100, 5); // 5 units in stock
const orderFactory = new OrderFactory();
const order = orderFactory.createOrder(customer, [product1, product2], [2, 3]); // 2 units of product1, 3 units of product2
console.log(`Total order amount: $${order.getTotalAmount()}`); // Total order amount: $350In this example:
- The
OrderFactoryensures that the customer is valid and that the products have enough stock. - It creates a valid
Orderwith the necessaryOrderItems and their quantities. - If any of the products are out of stock, the factory throws an error, preventing the creation of the
Order.
3. Enforcing Invariants in the Factory
Factories can also be responsible for enforcing certain invariants during the creation process. For example, the factory ensures that the stock for each product is sufficient before adding it to the order. This protects the integrity of the system by preventing orders from being placed with out-of-stock products.
4. Abstracting the Creation of Complex Aggregates
In DDD, aggregates are clusters of domain objects that are treated as a unit. The aggregate is represented by a root entity, and other entities within the aggregate are bound to the root.
A factory can be responsible for creating the entire aggregate. In our example, the Order is the aggregate root, and it contains multiple OrderItems. The factory ensures that the entire aggregate is created correctly.
For example, the OrderFactory creates the Order (aggregate root) and ensures that all OrderItems (part of the aggregate) are properly initialized and validated.
Factory Method vs. Constructor
Factories are used when creating an object involves more than just a simple constructor. For example, in our Order example, simply using the Order constructor wouldn't be enough:
- We would have to ensure that the products have enough stock.
- We would need to create the
OrderItems separately and add them to theOrder. - The
Orderitself might require some initialization logic (e.g., generating theorderId).
By encapsulating all this complexity within a factory, we simplify the client code and ensure that the object is created in a valid state.
Conclusion
In Domain-Driven Design, factories are used to encapsulate complex creation logic, ensuring that objects (especially aggregates) are created in a valid state. By using factories, we simplify client code and enforce business rules during object creation.
Next
Visit Repositories to dive deeper into Domain Driven Design.
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.