Aggregate Root references other aggregate roots
Asked Answered
B

3

29

I'm currently working a lot with DDD, and I'm facing a problem when loading/operating on aggregate roots from other aggregate roots.

For each aggregate root in my model, I also have a repository. The repository is responsible for handling persistence operations for the root.

Let's say that I have two aggregate roots, with some members (entities and value objects).

AggregateRoot1 and AggregateRoot2.

AggregateRoot1 has an entity member which references AggregateRoot2.

  1. When I load AggregateRoot1, should I load AggregateRoot2 as well?
  2. Should the repository for AggregateRoot2 be responsible for this?
  3. If so, is it okay for the entity in AggregateRoot1 to call the repository of AggregateRoot2 for loading?

Also, when I create an association between the entity in AggregateRoot1 to AggregateRoot2, should that be done through the entity, or through the repository for AggregateRoot2?

Hope my question makes sense.

[EDIT]

CURRENT SOLUTION

With help from Twith2Sugars I've come up with the following solution:

As described in the question, an aggregate root can have children that have references to other roots. When assigning root2 to one of the members of root1, the repository for root1 will be responsible for detecting this change, and delegating this to the repository for root2.

public void SomeMethod()
{
    AggregateRoot1 root1 = AggregateRoot1Repository.GetById("someIdentification");
    root1.EntityMember1.AggregateRoot2 = new AggregateRoot2();
    AggregateRoot1Repository.Update(root1);
}

public class AggregateRoot1Repository
{
    public static void Update(AggregateRoot1 root1)
    {
        //Implement some mechanism to detect changes to referenced roots
        AggregateRoot2Repository.HandleReference(root1.EntityMember1, root1.EntityMember1.AggregateRoot2)
    }
}

This is just a simple example, no Law of Demeter or other best principles/practices included :-)

Further comments appreciated.

Bureaucratize answered 7/2, 2011 at 9:28 Comment(8)
Personally I can see this current approach getting messy and I think DavidMasters84’s solution is more of an elegant solution. Ie keeping references as id's and extracting this type of domain logic to a domain service.Lucianaluciano
Messy is a good adjective for this approach. I'm allowed to say that because I originally tried implementing this problem in the same way, and a mess is what I found myself in :) you might want to read suggestions here also for a similar question: #2118588Harrietharriett
I hear you, but isn't repositories there to manage aggregate roots, and with some good will, relations between roots. And domain Services to handle behaviour that dosen't natural fit in a single entity? Seems to me that making the Domain Service resposible for handling references between roots is the wrong place when reading the definition of a Domain Service... I could be wrong, so a more backed up argument would be appreciated, thanks.Bureaucratize
My suggestion for a domain service isn't there to manage references between aggregates; it's there to invoke functionality that involves more than one aggregate i.e. "behaviour that doesn't naturally fit in a single entity". In most models all aggregates relate to each other in some form in terms of a relational databases, the point of aggregates is to break up this dependency graph into manageable groups. If you maintained the relationships between all aggregates in the model it would defy the point of aggregates.Harrietharriett
Yep that's pretty much my thoughts on it; your question is about running operations between aggregate roots which I think is where the domain service fits in. I think soon you'll be asking yourself how deep does the rabbit hole go...Lucianaluciano
tschmuck, I'm disagree with your solution and agree with DavidMaster. agg1.DoSomething(agg2); will work well as it is guarantee aggr1 object's integrity.Putrescent
As I read your suggestion, the reference between agg1 and agg2 will only be transient. The repository is responsible for persisting state and relations to what ever mechanism chosen. So how would you handle that relationship between aggregate1.Entity --> aggregate2. I need to be able to recreate the state and the relation. Are you sugessting that the Domain Service, or the object itself (agg1) should handle this?Bureaucratize
How did you get on with this? It seems to me you are caught up in data relationships rather than the required behaviour. If aggregate1 needs to change it's state based on aggregate2, it doesn't need to hold aggregate2 within it's own state, it can simply be passed aggregate2 via a method at the point in time when the functionality needs to be invoked. It seems most people agree with my solution...Harrietharriett
T
-5

Perhaps the AggregateRoot1 repository could call AggregateRoot2 repository when it's constructing the the AggregateRoot1 entity.

I don't think this invalidates ddd since the repositories are still in charge of getting/creating their own entities.

Tripitaka answered 7/2, 2011 at 9:40 Comment(6)
I considered this as well, but what if Aggregate2 has a reference to Aggregate3, and aggregate3 to another and so one. Potentially this could be a rather large object graph. What are the suggested strategies for these scenarioes?Bureaucratize
True, I know you're not supposed to worry about implementation too much in DDD but at that point Id lazyload the aggregates if I thought the graph is too large.Tripitaka
Ok, so far so good. :-) But when I'm creating the relation between AggregateRoot1.Entity1 --> AggregateRoot2, should this be done through the AggregateRootRepository1.AddRoot2ToEntity1(root1, root2) or through AggregateRepository2.AddRoot2ToRoot1(root1, root2) or a more direct assignment: root2.Entity1.AddRoot2(root2)Bureaucratize
Personally I'd say none of them. I'd assign it via this: "entity1.Entity2 = entity2" and then the repository should be able to detect this relationship and do what ever it needs to do (i.e. update the db colum if thats the underlying store)Tripitaka
Thank you TWith2Sugars, but I would really like to have some more feedback before I close this question. I appreciate your answers-Bureaucratize
I think your solution with the repository responsible for detecting any changes in relationsships to other aggregate roots, is a nice and clean one. The repository delegates any changes to referenced roots to the appropriate repository, and this repository can work in a smiliar way and delegate to others as well (in large graphs, considering lazy loading). I will edit my question to include how I would implement it, and give you credits for the answer. Thank youBureaucratize
H
62

I've been in this situation myself and came to a conclusion that it's too much of a head ache to make child aggregates work in an elegant way. Instead, I'd consider whether you actually need to reference the second aggregate as child of the first. It makes life much easier if you just keep a reference of the aggregate's ID rather than the actual aggregate itself. Then, if there is domain logic that involves both aggregates this can be extracted to a domain service and look something like this:

public class DomainService
{
    private readonly IAggregate1Repository _aggregate1Repository;
    private readonly IAggregate2Repository _aggregate2Repository;

    public void DoSomething(Guid aggregateID)
    {
        Aggregate1 agg1 = _aggregate1Repository.Get(aggregateID);
        Aggregate2 agg2 = _aggregate2Repository.Get(agg1.Aggregate2ID);

        agg1.DoSomething(agg2);
    }
}

EDIT:

I REALLY recommend these articles on the subject: https://vaughnvernon.co/?p=838

Harrietharriett answered 7/2, 2011 at 13:57 Comment(12)
+ 1. This is also what Vaughn Vernon argues for in his 'Reference Other Aggregate By Identity' rule. The advantages include overall Aggregate size leading to better performance and easier partitioning. The downside I guess is that it sometime makes the model code less expressive, so it's a trade off as usual: dddcommunity.org/sites/default/files/pdf_articles/…Octennial
With this approach you are not storing the relation between Aggregate1 and Aggregate2 in db, what will you do if there is a requirement to remember that relationshipMillepore
@Millepore - Yes you are. In the above example, Aggregate2 stores the ID of its related Aggregate1.Harrietharriett
@DavidMasters , ok i saw that now, but what u are not letting db maintain tat relationship in the above code line Aggregate2 agg2 = _aggregate2Repository.Get(agg1.Aggregate2ID); we don't know if 'Aggregate2ID' exists or not?Millepore
@Millepore to be honest I don't really know what you're saying. Whether a class has an ID property, full object reference, or nothing at all, has no effect on how that relationship is maintained in the database as a foreign key.Harrietharriett
@DavidMasters sorry if I was not clear, my confusion was since it is not a full object reference whether it still has a FK relation at db level, i was thinking more implementation here. So I guess what u r really saying is let there be no joins, but let the tables have a FK defined. The Application layer should decide if it needs the whole object from the Id in which case it queries the repository with the IdMillepore
@Millepore yes, that's basically what I'm saying. P.S. I can't recommend enough the link that Dmitry as put in his comment. Those PDF's provide the best explanation into aggregate design I've read.Harrietharriett
@Dmitry, the link is dead, do you know if the pdfs are around elsewhere?Runway
@DavidMasters, thank you very much - the articles are brilliant!Cripps
@Octennial The links is dead. Anyone know where I can find the pdf?Stereograph
@DavidMasters I have just one thing to ask about if I have Product Aggregate and BackLogItems Aggregate , and where BackLogItem is referencing the Product by Identity.Depending on your understanding could I use backLogItem.DoSomeInvaraintsChecking(product); for example I cannot add plan new BackLogItem in case the product is suspended.Guiscard
If I'm not confuse, it's an anemic domain model wrap up the actual rich domain isn't it?Battleax
V
0

This approach have some issues. first, you should have one repository to each aggregate and its done. having one repository that calls another one is a break on this rule. second, a good practice about aggregate relationship is that one root aggregate should communicate with another root aggregate by its id, not having its reference. doing so, you keep each aggregate independent of another aggregate. keep reference in root aggregate only of the classes that compose the same aggregate.

Volitant answered 30/11, 2020 at 12:51 Comment(0)
T
-5

Perhaps the AggregateRoot1 repository could call AggregateRoot2 repository when it's constructing the the AggregateRoot1 entity.

I don't think this invalidates ddd since the repositories are still in charge of getting/creating their own entities.

Tripitaka answered 7/2, 2011 at 9:40 Comment(6)
I considered this as well, but what if Aggregate2 has a reference to Aggregate3, and aggregate3 to another and so one. Potentially this could be a rather large object graph. What are the suggested strategies for these scenarioes?Bureaucratize
True, I know you're not supposed to worry about implementation too much in DDD but at that point Id lazyload the aggregates if I thought the graph is too large.Tripitaka
Ok, so far so good. :-) But when I'm creating the relation between AggregateRoot1.Entity1 --> AggregateRoot2, should this be done through the AggregateRootRepository1.AddRoot2ToEntity1(root1, root2) or through AggregateRepository2.AddRoot2ToRoot1(root1, root2) or a more direct assignment: root2.Entity1.AddRoot2(root2)Bureaucratize
Personally I'd say none of them. I'd assign it via this: "entity1.Entity2 = entity2" and then the repository should be able to detect this relationship and do what ever it needs to do (i.e. update the db colum if thats the underlying store)Tripitaka
Thank you TWith2Sugars, but I would really like to have some more feedback before I close this question. I appreciate your answers-Bureaucratize
I think your solution with the repository responsible for detecting any changes in relationsships to other aggregate roots, is a nice and clean one. The repository delegates any changes to referenced roots to the appropriate repository, and this repository can work in a smiliar way and delegate to others as well (in large graphs, considering lazy loading). I will edit my question to include how I would implement it, and give you credits for the answer. Thank youBureaucratize

© 2022 - 2024 — McMap. All rights reserved.