1

I've read a lot about Domain Driven Design including books from Eric Evans and Vaughn Vernon. So I am familiar with the concepts Aggregate Root, Entity, and Value Object.

But while I was modeling some domain model using Domain Driven Design approach a question arose which I never had before. I realized that I could model an aggregate root's state entirely as a value object which also includes the child entities. Let me first show you the "normal" approach:

class EntityDataVO {}

class Entity {
    String id;
    EntityDataVO data;

    Entity(String id, EntityDataVO data) {
        this.id = id;
        this.data = data;
    }

    void update(EntityDataVO data) {
        this.data = data;
    }
}

class AggregateRoot1 {
    private Map<String, Entity> entities = new HashMap<>();

    void addEntity(String id, EntityDataVO data) {
        this.entities.put(id, new Entity(id, data));
    }

    void updateEntity(String id, EntityDataVO data) {
        this.entities.get(id).update(data);
    }
}

There is an Entity class with an id and a value object EntityDataVO. The AggregateRoot1 class creates instances of this class and keeps a list of them. Updates to specific entities are delegated to the Entityclass.

Now let me show you the alternative modeling approach:

class AggregateRootDataVO {
    private Map<String, EntityDataVO> entities = new HashMap<>();

    AggregateRootDataVO addEntity(String id, EntityDataVO data) {
        var rootData = new AggregateRootDataVO();
        rootData.entities = new HashMap<>(entities);
        rootData.entities.put(id, data);
        return rootData;
    }

    AggregateRootDataVO updateEntity(String id, EntityDataVO data) {
        var rootData = new AggregateRootDataVO();
        rootData.entities = new HashMap<>(entities);
        rootData.entities.put(id, data);
        return rootData;
    }
}

class AggregateRoot2 {
    private AggregateRootDataVO data;

    void addEntity(String id, EntityDataVO data) {
        this.data = this.data.addEntity(id, data);
    }

    void updateEntity(String id, EntityDataVO data) {
        this.data = this.data.updateEntity(id, data);
    }
}

In this case, the list of entities is "encoded" into the AggregateRoot2's value object AggregateRootDataVO. As you can see, the value object's identity is defined by its members which are, of course, value objects by themselves. So two instances of this class with an equal internal map of entities are equal and I wouldn`t care which one to use.

Now I am confused. I really have no idea which alternative I should use. The second approach has definitely an advantage. The complete aggregate's state is represented as a value object. So it is easy to serialize it in order to send it over the network. For instance, a backend service could send it to a GUI frontend and the latter would see the complete aggregate's state. This is not true for the first approach implemented in AggregateRoot1. In that case I would have to define a DTO (Data Transfer Object) for that purpose which would look similar to the AggregateRootDataVO class.

I wonder what others think about those two modeling approaches and which one they'd prefer under what circumstances. Currently, I really don't know which one I should prefer. In fact, I am tempted to always prefer the second approach when there is no good reason against it, because I like to deal with value objects and their nice properties.

1
  • While it is possible in DDD to have a Value Object that references entities, it’s rare to find situations where it actually makes sense in a domain. Maybe if you show us a concrete example of your domain we can provide better insights.
    – Rik D
    Feb 21, 2020 at 7:26

2 Answers 2

2

I realized that I could model an aggregate root's state entirely as a value object which also includes the child entities.

Yes.

Fundamentally, the aggregate root is responsible for its entire data model. Carving that data model up into separate responsibilities is "arbitrary".

So how do we choose? I think one of the interesting ideas out there currently is Ward Cunningham's explanation of technical debt:

if we failed to make our program align with what we then understood to be the proper way to think about our financial objects, then we were gonna continually stumble over that disagreement and that would slow us down which was like paying interest on a loan.

More generally, while we "can" do anything that produces the right behaviors. But if we choose a design that is aligned with the "proper way" to think about our domain, we are going to have a much easier time managing future change.

In particular, when the domain expert asks us for a "small" change, the change to the implementation is more likely to be small if our design is aligned with the way the expert thinks about the domain.

5
  • Thank you for your answer.However, I don't see how it actually answers my question. It is a very generic one and, to me, it sounds like: "do the right thing". But I am asking for help to find the right choice in order to be able to do the right thing.
    – Jonny Dee
    Feb 20, 2020 at 16:28
  • 1
    @JonnyDee Well, perhaps this answer appears somewhat generic, but your question doesn't entirely make sense. The problem is conceptual; in your second implementation, you don't actually have a value object in the DDD sense. It's really still a collection of entities, except that you manage the storage of instances yourself (rather than relying on the underlying object facilities of the language). OK, that can sometimes be desirable. But, there's no semantic change here. If these objects are entities in the domain, modeling them as an immutable collection doesn't change that. Feb 20, 2020 at 20:26
  • 1
    @JonnyDee Furthermore, while not all designs are equally good, there's little sense in talking about "the right choice" in the context of a generalized, abstract example, without having a concrete problem domain; it's better to try and understand the tradeoffs. Because the right choice is the one that makes the code workable and maintainable given the needs of a concrete domain; the trick is to observe and recognize which tradeoffs to make in order to serve those needs. Feb 20, 2020 at 20:26
  • @Filip Milovanović My question doesn't entirely make sense? -- I disagree. In my second implementation I don't have a value object in the DDD sense? -- I disagree. It manages the storage of instances (rather than relying on the underlying object facilities of the language)? -- I disagree and I don't think this statement is a correct one at all. Are you sure you understood my problem? In short, the difference between those two implementations is, that the list of entities is represented in an immutable value object. This hasn't anything to do with not using "object facilities of the language".
    – Jonny Dee
    Feb 21, 2020 at 5:36
  • @JonnyDee "the difference between those two implementations is, that the list of entities is represented in an immutable value object" - I understand perfectly well, and I'm telling you that the actual representation in code does not in itself make it a DDD value object; these are not the same - one is an implementation detail, the other is a semantic role the concept has in your application, and for the people using it. If you have to take into account that something has conceptual identity in the domain (i.e., it is an entity), representing it as an immutable value doesn't change that. Feb 21, 2020 at 8:23
1

DDD will not provide an idiomatic answer to your question. Your first option requires less code. Go with that.

I don't see an important difference between the two examples you have provided. They are equal implementations of the same Aggregate. Similarly, other than affording the exact signature and terseness for your updateEntity method, I don't see a reason for the existence of EntityDataVO.

The key insight here is that the consumers of either object (Entity and Aggregate) will not know (or care) how the internals of your domain are implemented, so why bother spending (wasting) time on this? You should choose the most straightforward method available that meets your business requirements.

On a broader note, because you are abusing the term, we need to provide some clarity. Your VO's aren't really Value Objects in the DDD sense, rather, technical artifacts used for your own internal implementation of immutability. A Value Object is not just an immutable object. It is also a domain concept. This means, in a business sense, it is a useful representation of and/or to your domain.

Your examples are only useful to their wrappers. There's nothing wrong with that! If you want to model your entity's internal machinations as immutable using the approach you have outlined above, that is your prerogative. Though from a DDD standpoint it makes no difference.

6
  • First of all, thank you for your answer. "I don't see a reason for the existence of EntityDataVO". The reason why I didn't use meaningful names of a concrete domain is that I wanted to avoid answers like "Maybe your are modeling your domain in a wrong way". I wanted to discuss the pattern itself, rather than its application to a concrete domain. Just like design patterns abstract away concepts from a concrete domain. I am aware of the fact that a Value Object in DDD must represent a domain concept. I should have emphasized that I assumed this is the case.
    – Jonny Dee
    Feb 25, 2020 at 8:31
  • @JonnyDee I understand what you are saying. I guess my only question then is... Why does EntityDataVO exist? The implementation you provide above makes it look like an implementation detail. Does your Entity actually have a bunch of other fields not shown? My skepticism is precisely because of the pattern you are describing. Separating the data from the id within an entity doesn't provide any value to the domain. I does potentially provide value to those developing the domain though. Feb 25, 2020 at 15:53
  • Separating data / id may provide value. Example: AggregateRoot2=Job, AggregateRootDataVO=JobDescription, Entity=Task, EntityDataVO=TaskDescription. A Jobs tasks have unique names as ids (within a job, of course). All concepts are part of the Ubiquitous Language of a "Job Scheduling" domain. Now both VOs are VOs in the DDD sense (equal descriptions are indistinguishable). There might be two or more Jobs with different ids that happen to have identical job descriptions at some point in time. Now if I wanted to find jobs doing the same I can just compare their descriptions.
    – Jonny Dee
    Feb 25, 2020 at 16:31
  • @JonnyDee Your example above could be equally modeled as just a string property of Job. The only reason to separate the description into a Value Object would be if some other parts of the domain need to have knowledge of, and therefore logic based on, JobDescriptions independently of Jobs (so not like in your example behavior). Imagine we have Car {vin: string, make: Make, ...} in some auto-related domain. It makes sense to model Make as a Value Object because it represents it's own concept (not "a property of Car") and is likely used in many ways independently from Car. Feb 25, 2020 at 18:31
  • Now you know, why I didn't provide meaningful names in the first place. The discussion now shifted away to how I could model the job description. But now that you started this discussion: I assumed a textual description for a job is not sufficient, because it consists of several tasks that can be executed. And a task's description is not a string either, because it will contain values the task needs to consider when it is executed. Anyway, this example is just that -- an example. And I just provided one, because you said separating an entitiy's id from its data doesn't make sense.
    – Jonny Dee
    Feb 25, 2020 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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