DDD: can a Repository return entities inside an aggregate?
Asked Answered
P

1

10

I have a City aggregate, having a list of PointOfInterest entities. This latter entity lies logically inside the City aggregate for reasons that won't be explained here. No entity holds a link to PointOfInterest, apart from the aggregate root, City.

However, we have a web page for PointOfInterest, browsable from the City page, that (mainly for SEO reasons) only has the PointOfInterest id in its URL.

Thus, from the controller, it would be handy to query the CityRepository for a PointOfInterest directly, such as CityRepository.findPointOfInterestById().

The other option would be to query CityRepository.findCityByPointOfInterestId(), then City.findPointOfInterestById(), which looks a bit cumbersome in this case.

Is there anything wrong with the first approach?

Piezochemistry answered 5/8, 2012 at 10:20 Comment(2)
'For reasons that won't be explained here', maybe that's the point. If your PointOfInterest is really inside your City aggregate, it wouldn't be a good design to query for it separately. If you really need to fetch it without the city, it's probably not a apart of the City aggregate.Recrement
I was expecting this comment TBH, but actually City has a collection of PointOfInterestType, which in turn has a collection of PointOfInterest. So that would force me to create a PointOfInterestRepository, and a PointOfInterestTypeRepository, which is even more smelly in my opinion, for this simple requirement (don't forget that I could include the City id as well in the URL, but just omitting this redundant information for clean URL purposes).Piezochemistry
M
5

Since PointOfInterest is part of the City aggregate, you have to accept that all references to PointOfInterests must be obtained by traversal of a City. It doesn't seem appropriate for CityRepository to expose GetPoI() methods because it would allow an outside object to get a direct reference to a PoI, defeating the whole purpose of an aggregate. Besides, it doesn't really seem like a natural responsibility of a CityRepository to deal with PoIs.

As Wouter points out, your feeling the need to do convoluted things like this might be a sign that the current design is not quite appropriate. If PointOfInterest turns out to be one of the major entities in your application and if the user can access it right from the start of his navigation without necessarily going through a City first, why not consider giving PoI its own aggregate ? As for the PoI / PoIType relationship, it seems more logical for a PoI to have a PoIType than the other way around.

Medora answered 7/8, 2012 at 13:7 Comment(8)
I get your point. However, as explained above, you first navigate to a City, then to a Poi. The City id is not included in the URL to keep it simple, but could very well be otherwise, allowing traversal from the City without any problem. That's what makes me believe that Poi is right in the City aggregate. I'm not totally opposed to it having its own Repository (that's how it was before!), but that forces me to have a PoiTypeRepository as well, and that becomes quite messy IMHO. If you have any idea to avoid the PoiTypeRepository, I might reconsider my choice!Piezochemistry
Could PoiType be a Value Object ? If it could, then both PoI and City could reference it without any problem, especially if it is immutable. Instead, if PoiType has an identity of its own and you have to track changes to PoiTypes, then maybe PoiTypeRepository is the way to go. You might find interest in this series of articles by Vaughn Vernon (dddcommunity.org/library/vernon_2011) where he advocates the use of small aggregates under some circumstances.Medora
Other than these 2 approaches and the City.findPointOfInterestById() solution, I'm afraid there's no simple way to solve your problem...Medora
I will have a look, thanks. But I'm not sure of my understanding of DDD: when you say "it would allow an outside object to get a direct reference to a PoI", if I remember well Fowler said that while an object could not keep a permanent reference to an entity within an aggregate (which I understand as a foreign key in the DB), it can be handed temporarily a reference to such an entity, for example as a method parameter, as long as the object does not store this reference. So what's wrong with returning a PoI directly, as long as no other entity has a link to it?Piezochemistry
You must mean Evans ? You're right, I put it badly, I meant "a direct reference to a PoI without obtaining it from its root", which can cause invariant enforcement and consistency problems. I also find CityRepository.findPointOfInterestById() more cumbersome and misplaced than City.findPointOfInterestById() or City.PointsOfInterests[id] by the way...Medora
Yes, I did mean Evans :) I still can't see what kind of invariant enforcement I might break with this approach, though?Piezochemistry
Invariants of entities inside the aggregate that are checked when the root is saved or loaded for instance. These shouldn't be very common though.Medora
Right for me if the PoI returned is an inmutable copy (so the client that get cannot change)Synge

© 2022 - 2024 — McMap. All rights reserved.