I'm reading Vernon's article Effective Aggregate Design. And I have a question about why modifies only one aggregate instance per transaction?
Let's take an example, consider a Warehouse invertory management story.
Inventory represents an item with quantity in a warehouse. 5 Implementing Domain Driven Design books in Shanghai warehouse for instance.
Entry represents a log about an in/out operation on an Inventory. Entering 2 Implementing Domain Driven Design books in Shanghai warehouse for instance.
An Inventory's quantity need to be changed if an Entry is submitted.
It easily comes to my mind, this is an invarient could be implemented by transactional consistency.
Solution A: Using one Aggregate and cluster Entry into Inventory.
public class Inventory implements Aggregate<Inventory> {
private InventoryIdentity id;
private Sku sku;
private int quantity;
private List<Entry> entries;
public void add(Entry entry) {
this.quantity += entry.getQuantity();
this.entries.add(entry);
}
}
public class Entry implements LocalEntity<Entry> {
private int quantity;
// some other attributes such as whenSubmitted
}
public class TransactionalInventoryAdminService impelments InventoryAdminService, ApplicationService {
@Override
@Transactional
public void handle(InventoryIdentity inventoryId, int entryQuantity, ...other entry attributes)
Inventory inventory = inventoryRepository.findBy(inventoryId);
Entry entry = inventory.newEntry(entryQuantity, ..);
inventory.add(entry);
inventoryRepository.store(inventory);
}
}
Solution B: Using seperate Aggregate for Inventory and Entry.
public class Inventory implements Aggregate<Inventory> {
private InventoryIdentity id;
private Sku sku;
private int quantity;
public void add(int quantity) {
this.quantity += quantity;
}
}
public class Entry implements LocalEntity<Entry> {
private Inventory inventory;
private int quantity;
private boolean handled = false;
// some other attributes such as whenSubmitted
public void handle() {
if (handled) {
throw .....
} else {
this.inverntory.add(quantity);
this.handled = true;
}
}
}
public class TransactionalInventoryAdminService impelments InventoryAdminService, ApplicationService {
@Override
@Transactional
public void handle(InventoryIdentity inventoryId, int entryQuantity, ...other entry attributes)
Inventory inventory = inventoryRepository.findBy(inventoryId);
Entry entry = inventory.newEntry(entryQuantity, ..);
entry.handle();
inventoryRepository.store(inventory);
entryRepository.store(entry);
}
}
Both A and B are feasible, but solution B is kind of inelegant for leaving inadvertent oppertunity to invoke Inventory.add(quantity) without Entry involved. Is this what the rule (Modifies only one aggregate instance per transaction) tries to point out for me? I'm confused mostly why we should modify only one aggregate in a transaction, what goes wrong if we don't.
Update1 start
Is it intend to alleviate concurrency problems (with another rule of "make smaller aggregates")? For example, Entry is an Aggregate with relatively low contention and Inventory is one with relatively high contetion (assuming that multiple user could manipulate one Inventory), it causes unnecessary concurrency failure if I modify them both in a transaction .
Update1 end
Some further problems need to be addressed if I adopt solution A:
1.What if there are many Entry s for an Inventory and I need a paged query UI? How to implement a paged query using Collections? One way is to load all Entry s and picks what the page need, the other way is InventoryRepository.findEntriesBy(invoiceId, paging), but this seems to break the rule of get an local entity only by get it's aggreate then navigate the object graph.
2.What if there are too many Entry s for an Inventory and I have to load all of them when add an new Entry?
I know these questions stem from lacking full understanding. So any idea is welcome, thanks in advance.
Aggregates. The stategic DDD's bounded contexts typically contain multiple aggregates. Within the aggregates, you can model your system with the tactic DDD patterns, such as Entity, Value Object, Domain Event, Service and Repository. The page Aggregate describes how you can create aggregates.
I suppose, that it's fine to have maximum 3-4 aggregates per single bounded context. If there are more aggregates in single bounded context, then there are probably some issues with the software design.
Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate.
Rule of thumb is to keep your aggregates small, since you want to avoid transactional failures due to concurrency. And why would we make the memory footprint big if it shouldn't be?
So, solution A is not optimal. Big aggregates often introduce problems that easily can be avoided.
It's true that another rule of thumb is to only change one aggregate in one transaction. If you make Entry it's own aggregate, you can make the inventory's quantity eventual consistent, meaning the Entry aggregate could raise an event to which the inventory is subscribed. This way you're only changing one aggregate per transaction.
public class Entry {
public Entry(InventoryId inventoryId, int quantity) {
DomainEvents.Raise(new EntryAdded(inventoryId, quantity))
}
}
If you don't feel comfortable with eventual consistency, you can still keep the aggregates separate, but modify them both in one transaction for now - until you're feeling the pain, using an encapsulating domain service. Another option is to keep the domain events in process, so that they're also committed in a single transaction.
public class InventoryService {
public void AddEntryToInventory(Entry entry) {
// Modify Inventory quantity
// Add Entry
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With