1

Suppose I have a User entity with name and age attributes. A User can own Boxes. 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.

3
  • It's difficult to compose a satisfying answer in this case because the behavior of a system is what should inform the design. Given the behavior above there is no reason a User needs a Box collection at all! It could (and I'd argue should) be modeled such that the User only needs a private numberOfBoxes: int property to enforce our single invariant. In this way we can avoid the "problem" you lay out entirely. May 20, 2019 at 16:27
  • I see, and agree with the 'behaviour modeling' approach. With the 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. May 20, 2019 at 17:07
  • Correct. I'll compose an example below. May 21, 2019 at 16:07

1 Answer 1

2

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 Boxs 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.

1
  • 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. May 21, 2019 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.