Suppose I have a User
entity with name
and age
attributes. A User
can own Box
es. A Box
has the name
and color
attributes. Business rules dictate that one User
can own at most 5 boxes. So, in this case, the User
can be the aggregate root and whenever a new Box
is bought by a User
, I can do
class User {
private boxes: Box[];
private MAX_BOXES = 5;
// snip
addBox(Box box) {
if (this.boxes.count > MAX_BOXES) {
throw new BoxLimitException();
}
this.boxes.add(box);
}
}
However, when I want to edit one Box
's (or a User
, for that matter) attributes, the box limit business rule is no longer needed so I should be able to just modify it directly, without going through the aggregate, i.e.:
box->paint(GREEN);
But, according to what I've read on DDD, you're not supposed to do that, since you're breaking the bounded context of the aggregate root. Should I have different Box
entities depending on the use case? Specially since a Box
might have its own business rules independent of those of a User
aggregate.
I already asked a similar question, but it got no answers, so I tried with this new, simpler example, to better explain my doubts.
1 Answer 1
The most common pattern used to separate a group of entities given invariants that span between them is to break down the use-case into a series of steps and employ value objects/factory methods as a means of communication.
I am going to extend your example to help illustrate what I mean. Let us introduce a new concept, Container
, to which Box
s are added (instead of User
), and one more invariant: A Box
can only be added to a Container
by a User
that has completed payment. Given our two invariants, how can we model this in a way to keep Container
, Box
and User
as separate entities?
class User
{
private userId: int;
private hasCompletedPayment: bool;
public AddBoxIntent addBox(string content)
{
if (!this.hasCompletedPayment) {
throw new UserCannotAddBoxException();
}
return new AddBoxIntent(this.userId, content);
}
}
// value object
class AddBoxIntent
{
public readonly userId: int;
public readonly content: string;
}
class Container
{
private containerId: int;
private numberOfBoxes: int;
private MAX_BOXES = 5;
public Box addBox(AddBoxIntent intent)
{
if (this.numberOfBoxes >= this.MAX_BOXES) {
throw new BoxLimitException();
}
this.numberOfBoxes++;
return new Box(this.containerId, intent.userId, intent.content);
}
}
// Box not shown
We can see above that User.addBox
is a factory method used to obtain permission to add a Box
to any Container
at some later point in time (guarding our payment invariant), and Container.addBox
is a factory method used to produce a new Box
related to our User
and Container
.
A use-case may then look like:
// within our service
user = users.find(cmd.userId);
container = containers.find(cmd.containerId);
intent = user.addBox(cmd.content); // may throw
box = container.addBox(intent); // may throw
boxes.save(box);
In this way we are allowing all three of our entities to be loaded/changed independently of one another should be want to perform modifications in other ways.
-
I love this. In all my research I'd never read about the Intent patter applied in DDD. It's so clean! Thanks so much.dddwonderer– dddwonderer2019年05月21日 17:54:22 +00:00Commented May 21, 2019 at 17:54
Explore related questions
See similar questions with these tags.
User
needs aBox
collection at all! It could (and I'd argue should) be modeled such that theUser
only needs aprivate numberOfBoxes: int
property to enforce our single invariant. In this way we can avoid the "problem" you lay out entirely.numberOfBoxes
property you suggest, how would I go about adding a Box to the user? In my service I'd have to load the repository for both the User and the Box, and doing the invariant check on the User aggregate, and afterwards persist the Box aggregate. Is that right? I know better than following methodologies blindly rather than pragmatically, but DDD seems so rigid that I have these kind of doubts pretty much every time I have to model anything.