Tactical Domain-Driven Design: Patterns for Complex Systems
Amir Goalmoradi
Backend Developer | Java | Spring | DDD | Hexagonal | Clean Architecture | TDD | SQL/NoSQL | Docker
Welcome back to our Domain-Driven Design (DDD) series! In our previous discussions, we introduced DDD’s core concepts and strategic design principles. Now, it’s time to get tactical by exploring key patterns that help us model complex domains effectively.
Core Building Blocks in DDD
At the heart of Tactical DDD are three fundamental concepts:
? Entities – Objects with a unique identity that persists over time.
? Value Objects – Immutable objects defined by their attributes, not identity.
? Aggregates – Groups of related objects that form a consistency boundary.
Let’s break them down with some examples!
1. Entities – Objects with Identity
An Entity is an object that has a unique identity and a lifecycle. Even if its attributes change, its identity remains the same.
In an e-commerce system, a Customer is an entity because it has a unique ID, and its details (name, email) can change over time.
@Getter
@Setter
public class Customer {
private final Long id; // Unique identity
private String name;
private String email;
public Customer(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
When to Use Entities?
?? Use Entities when an object has a unique identity that must persist over time.
?? Ideal for objects like Users, Orders, Employees, Accounts, etc.
2. Value Objects – Immutable & Identity-Free
Unlike entities, Value Objects do not have a unique identity. Instead, they are defined by their attributes and should be immutable.
Money is a perfect example of a Value Object because $100 USD is equal to another $100 USD, regardless of where it comes from.
@Getter
public final class Money {
private final double amount;
private final String currency;
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// Example of a method to add amounts
public Money addAmount(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount + other.amount, this.currency);
}
}
Key Characteristics of Value Objects
?? No unique identity – They are compared by their attributes, not a unique ID.
领英推荐
?? Immutable – Once created, their state cannot change.
?? Reusable – Used to represent concepts like money, addresses, coordinates, dates.
---
3. Aggregates – A Cluster of Related Objects
An Aggregate is a collection of related Entities and Value Objects that form a single unit of consistency. The Aggregate Root ensures all business rules are enforced.
In an e-commerce system, an Order consists of multiple OrderItems. Instead of allowing direct modification of OrderItems, we define Order as an Aggregate Root to manage them safely.
// Order Aggregate Root
import java.util.ArrayList;
import java.util.List;
public class Order {
private final String id;
private final String customerId;
private final List<OrderItem> items = new ArrayList<>();
public Order(String id, String customerId) {
this.id = id;
this.customerId = customerId;
}
public void addItem(String productId, int quantity) {
items.add(new OrderItem(productId, quantity));
}public List<OrderItem> getItems() {
return new ArrayList<>(items); // Defensive copy
}
}
@Getter
@Setter
public class OrderItem {
private final String productId;
private final int quantity;
public OrderItem(String productId, int quantity) {
this.productId = productId;
this.quantity = quantity;
}
}
Why Use Aggregates?
?? Ensures consistency – The Aggregate Root controls changes to its members.
?? Reduces complexity – Provides a clear structure for handling related objects.
?? Improves performance – Prevents unnecessary database queries by managing related data together.
When to Use What?
Conclusion
In this article, we explored the tactical design patterns that form the foundation of Domain-Driven Design (DDD) in Java:
? Entities represent objects with a unique identity.
? Value Objects are immutable and defined by their attributes.
? Aggregates group related entities to ensure consistency.
By applying these principles, we can better structure complex domains and make our business logic more maintainable, scalable, and testable.
Next Up: In the next article, we’ll explore Repositories and Unit of Work—patterns that help us manage persistence and transactions seamlessly.
?? Stay tuned, and happy coding! ????
Back-end Software Engineer | Software Integration | API Security | OAuth2 | Content Creator
1 个月Thank you for this article! I am not that proficient on DDD and this was crystal clear for me! Thanks a lot!
Backend Developer | Java | Spring | DDD | Hexagonal | Clean Architecture | TDD | SQL/NoSQL | Docker
1 个月Great news Everyone - the next part is now available! You can find it here: https://www.dhirubhai.net/pulse/repository-unit-work-domain-driven-design-ddd-amir-goalmoradi-zvp4f/?trackingId=DapfJPZAQFmZzjzIbuxyAA%3D%3D ?? Looking forward to hearing your thoughts on the next installment! ??
Senior .NET Developer | C#, ASP.NET Core, EF Core | Microservices, Azure
1 个月Clear, concise, & practical :) A gr8 breakdown of DDD with solid examples. Looking forward to the next one ??