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 Entity
class.
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.