Aggregate child entities and event sourcing

2,305 views
Skip to first unread message

Eric Quist

unread,
Feb 8, 2011, 4:17:25 PM2/8/11
to DDD/CQRS
When trying to learn DDD with event sourcing, I took Greg's SimpleCQRS
sample (https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS)
and wanted to create a sample with an aggregate that is more than only
an aggregate root (and value objects).

I have a Project that inherits AggregateRoot, and I have created a
Activity entity. So the Project aggregate root has a collection of
activities that is part of the same aggregate (for the sake of this
discussion I don't want to have suggestion about Activity not beeing
part of the aggregate). The classes is simplified, but I still hope
I'm able to explain my concern.

public class Activity : Entity
{
private Guid id;
private string name;
private AggregateRoot aggregateRoot;

public override Guid Id
{
get { return id; }
}

internal Activity(AggregateRoot root, Guid id, string name)
{
Contract.Requires<ArgumentNullException>(root != null);

this.aggregateRoot = root;
this.id = id;
this.name = name;
}

internal static void CreateNew(AggregateRoot root, Guid id,
string name)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(id !=
Guid.Empty);
Contract.Requires<ArgumentNullException>(!
string.IsNullOrWhiteSpace(name));

root.ApplyChange(new ActivityAdded(root.Id, id, name,
null));
}

public void Rename(string newName)
{
Contract.Requires<ArgumentNullException>(!
string.IsNullOrWhiteSpace(newName));

aggregateRoot.ApplyChange(new ActivityRenamed(id, name));
}

public override string ToString()
{
return name;
}
}

public class Project : AggregateRoot
{
private Guid id;
Dictionary<Guid, Activity> activities = new
Dictionary<Guid,Activity>();

public override Guid Id
{
get { return id; }
}

public void AddActivity(Guid activityId, string name)
{
Activity.CreateNew(this, activityId, name);
}

public Activity GetActivity(Guid id)
{
return activities[id];
}

private void Apply(ActivityAdded e)
{
activities.Add(e.Id, new Activity(this, e.Id, e.Name));
}

private void Apply(ActivityRenamed e)
{
//what to do here?
}
}

So this is the best I have managed to create so far, and now I hope
for some help from you all again :)

What I have managed to do with this design:
* The logic for the behaviors are still encapsulated in the Activity
entity, so that it isn't just a container of data.

What I don't like with this design:
* I'm not satisfied with the added dependency that activity now has to
the aggregate root, but I could live with that
* How should Project.Apply(ActivityRenamed) be handled? It can't call
Rename again of course and I don't want it to access the name field
(in Activity).

How would you improve this design?

I haven't managed to find a single example of event sourcing where the
aggregate contains anything else but the aggregate root and value
objects. Is there any good examples of CQRS/Event Sourcing that covers
a little bit more?

Thanks again, Eric

Elliott O'Hara

unread,
Feb 8, 2011, 7:32:08 PM2/8/11
to ddd...@googlegroups.com
Eric,
What behavior does an Activity have?
/E

Eric Quist

unread,
Feb 9, 2011, 3:26:12 AM2/9/11
to DDD/CQRS
I'm sorry, but I don't understand what the behaviors of the Activity
has to do with this question? It is not about the domain, but rather
general:
How should you handle the relationsship between the aggregate root and
the entities belonging to the same aggregate? With focus on the fact
that the "event stream" is on the aggregate root.
> > Thanks again, Eric- Dölj citerad text -
>
> - Visa citerad text -

Nuno Lopes

unread,
Feb 9, 2011, 5:51:31 AM2/9/11
to ddd...@googlegroups.com

For simple cases try to think the internals of an Aggregate has Data Structures encapsulating State.

For more complex cases you can use SOM approach (Streamlined Object Modeling).

An event in SOM is a Transaction over which multiple domain objects collaborate.

- To go from command to event the Aggregate Root creates the Event and adds collaborations (entities and value objects) to the event and let the event direct the collaboration;
- To go from event to collaborators again let the Aggregate find collaborators based on the event data (none found create), and call apply on each of them.

So for instance say we are calling changeProduct to an AR Order, we get the event ProductOrderChanged

changeProduct(Guid productid, int numberOfItems) ->

ProductOrderChanged e = new ProductOrderChanged() ->
Product p = this.listOfProducts[productId];
Customer c = this.customer;
e.addCustomer(c);
e.addProduct(p);
e.addNumberOfItems(numberOfItems);
e.addOrder(this);
e.apply();

apply() ->
e.product.apply(this);
e.order.apply(this);
e.customer.apply(this);

Don't have time to make an example, but read the implementation samples of SOM around transactions, and adapt accordingly with the pointer above.

Cheers,

Nuno

Daniel Yokomizo

unread,
Feb 9, 2011, 6:06:47 AM2/9/11
to ddd...@googlegroups.com
On Wed, Feb 9, 2011 at 6:26 AM, Eric Quist <equ...@hotmail.com> wrote:
> I'm sorry, but I don't understand what the behaviors of the Activity
> has to do with this question? It is not about the domain, but rather
> general:
> How should you handle the relationsship between the aggregate root and
> the entities belonging to the same aggregate? With focus on the fact
> that the "event stream" is on the aggregate root.

A few points here:

* An entity have no identity outside the aggregate, so a guid is an
overkill. Usually I find that entities have an external identifier
(e.g. name) that can be used instead. If this identifier is mutable
then usually either it's not the true identifier or it starts an
interesting conversation about what actually is the identity of this
entity.
* IME it's better to source all events from each Entity but decorate
them on the AR:

Activity.Rename(string newName) {
ApplyChange(new ActivityRenamed(newName));
}
Activity.Apply(ActivityRenamed evt) {
this.name = evt.NewName;
}

Project.Rename(Guid id, string newName) {
var activity = activities[id];
activity.Rename(newName);
}

Project.Apply(ActivityAdded evt) {
var activity = new Activity(this, e.Id, e.Name);
activities.Add(e.Id, activity);
activity.On(evt => ApplyChange(new ProjectActivityChanged(evt.Id, evt)));
}

External consumers can't see ActivityAdded without the external
ProjectActivityChanged envelope.

Best regards,
Daniel Yokomizo

Bhoomi

unread,
Feb 9, 2011, 7:57:35 AM2/9/11
to ddd...@googlegroups.com
There is an example in nCQRS.

keith Pope

unread,
Feb 9, 2011, 9:28:38 AM2/9/11
to ddd...@googlegroups.com
On 9 February 2011 12:57, Bhoomi <bhoomi....@gmail.com> wrote:
> There is an example in nCQRS.
> Pl refer to : https://github.com/pjvds/Scrumr

Hmm I think I may have been thinking about Aggregates the wrong way,
so is it typical to just store lists of guid's inside the AR instead
of the actual entities?

--
------------
http://www.thepopeisdead.com

Reply all
Reply to author
Forward
0 new messages