DDD: aggregate root question
Asked Answered
K

3

16

Let's say i got 2 entities - Foo and Bar. Foo is an aggregate root and contains Bar. As far as i understand, it should look like this:

public class Foo{
    private readonly Bar Bar;
}

I want to provide functionality for users to choose Bars for Foos from a defined list (and change it).

If repositories are supposed to be for aggregate roots only it means that there will be no repository for Bar entity.

This leads to problem - Bar can't be created/updated independently without a reference to Foo.

Does that mean that Bar is supposed to have a repository despite that it has no meaning without a Foo?

Killick answered 25/9, 2009 at 8:53 Comment(1)
No such thing as a stupid question, just stupid answers ;) BTW this question helped me a lotTasimeter
D
17

If you want to select from a list of Bars where they're not associated with Foo, then this is not an aggregate root. For example, you can't get list of OrderItems without their Order, so this is single aggregate root (Order), but you can get list of Products to assign to OrderItems, so Product is not part of the Order aggregate root.

Notice that while OrderItem is part of Order aggregate root, you can still create and update it independently. But, you cannot get it without reference to Order. Same for your Bar, even if it was part of Foo, you could get each(Foo.Bars) and work with it, or do Foo.AddBar(new Bar()). But if you need to get List without Foo, Bar is not part of Foo aggregate. It is a separate entity.

Well, that's how I see DDD here, but I'm not Eric Evans, of course.

Demasculinize answered 25/9, 2009 at 17:39 Comment(7)
This makes sense. If you got anything else to say - go on. :)Killick
Thing that i learnt - there are too many aggregate roots in my domain. There are entities made as roots despite that they shouldn't be updated independently. Even worse - some roots can be replaced as value objects.Killick
Another suggestion that comes to my mind, is that if your Bar is a standalone (can be presented as a separate list) and dependent (works inside Foo) at the same time - maybe you actually have two entities here. A short example: Group { Name, Price } - should be both inside Order/Product and presented separately, but if you split it into Group { Name } and ProductGroup { Group, Price }, you present Group separately, while keeping ProductGroup as part of aggregate.Demasculinize
@queen3; I believe I understand your point but could you just clarify something for me. Using a different example...Customer is an agregate root which has a country. In order to set the country you need to select from a list of countries...so that makes country an agregate root as well...correct?Andresandresen
Well, I'd say this makes Country a separate entity. It will be an aggregate root if it contains other entities (e.g. Region). That's if I correctly understand that "aggregate root" and "entity" are different beasts ;-) But it's definitely not part of the Customer aggregate root. Well, in some situations it can be part of Customer... e.g. in a virtual world game where users create their own countries, a country may be a part of the "player's World". But I guess that's not your case.Demasculinize
That makes sense then...country in my case is actually just an entity as it does not contain regions (though maybe it should) but it is like a root entity if I can call it that which means that it should have it's own repo. Customer is an aggregate root of the Customer/Country aggregate and as such has it's own repo for making changes on it. Appreciate your opinions, thanks!Andresandresen
Another clarifications is, as far as I understand, aggregate roots are not meant to contains their internal entities only, they can also have "external" entities. Classic example I guess is Order/Item, and we can of course expect Order to have Reseller, and Reseller is even more clearly a separate "external" entity. But I sometimes wonder if those terms matter at all ;-)Demasculinize
S
8

The reasons for having Aggregate roots are:

  1. They provide controlled and directed access to composite entities
  2. They can enforce rules to ensure that the entire aggregate is valid

My take: If you need to select Bar objects without a Foo, use a BarRepository.

But... What if you update a Bar, and it breaks a validation rule for it's parent Foo? If this could happen, you should access Bar via it's parent Foo.

If, however, you need to access a bunch of Bar objects (e.g for a batch job or report), and you know that Foos won't get broken, go ahead and access them via BarRepository.

Remember that aggregate roots can be composed of other aggregate roots. You may discover that Bar is an aggregate root itself, giving you justification for a BarRepository :)

Standin answered 26/9, 2009 at 19:44 Comment(0)
R
2

Are you sure that Bar need to be a entity? Do you have the need to track it and change it in the domain? If you can look at it as a value object, I would suggest that you fetch it from a service and then "connect" the selected value object to the Foo entity. For instants through a dropdown list.

Roommate answered 25/9, 2009 at 17:25 Comment(4)
And why entity can't be fetched through service and connected to Foo?Killick
How value objects are supposed to be created/updated without context of entity? Wouldn't that be masking service as repository which value object shouldn't have?Killick
Let me explain how I have done this; I need a way to get data from the persistent layer. I could use a DTO to fetch the data, but I rather use a value object because then I can use the object in the domain without mapping from the DTO to the value object. The object I'm using is a class from the aggregate root, in your case Bar. If I were to do this for an entity, I guess I would use a DTO (fetched through a service) to fill the list (combobox etc) and when I have selected the right Bar I would ask the repository to get me the complete object from the aggregate root. Hope this makes sense.Roommate
I wonder if this solution could apply at all to my question about validation and choosing defaults based on data that is otherwise external from the aggregate. #5455021Cavie

© 2022 - 2024 — McMap. All rights reserved.