Usually whenever I want to fetch an aggregate root by ID I just use some type of Repository::findByID(...) function

Whenever I started with DDD I thought factories where just a pattern to build new objects, but after meeting some aggregates needing one or two extra queries to load, I realized that Factory::objectWithID(...) were useful to also create instances of objects already existing in the database.

Now I have a tree relationship of entities, like Project->Task. The number of relations is huge and I have no framework providing lazy loading.

Since Tasks can be nested, have complexity of their own, and should not be fully retrieved in one query, I made Project and Task different aggregate roots

How should I retrieve and persist Tasks?. It seems that a ProjectFactory is not the solution this time because a Project does not contain the whole Task tree.

I still want some aggregate root like features for my Project, and since I am avoiding queries inside my entities, I decided to write the relationships inside a Project-Service-Aggregate. Now I can retrieve a single Project with a Repo::findByID() function, but fetching a Task looks like

ProjectAggregationService *service = ProjectAggregationService.new(SomeProject)
Task task_1 = service.findTask(...)
Task task_1_child_2 = service.findTaskChild(task_1,2)

I am puzzled at this point because:

  1. I have a service to represent entity relationships.
  2. I am declaring many instances of a service, whereas before, services tended to be quite unique objects.
  3. Not having a clear object tree like Project.tasks[1].subtask[3] make the code above look like more complex than necessary.

Basically I could summarize my question to: Did I take the right approach?

I would greatly appreciate any comment on my reasoning. I am mostly concerned about degenerating my code with overblown complexity, but I still think that keeping references to queries and repos outside my entities implementation is a good goal.

  • If you don't have a framework for lazy loading, why don't you add that (framework or lazy loading), as it seems to be just what you want? – Frank Jul 19 '13 at 11:13
  • 1
    I believe that there are common DDD solutions to this problem without lazy loading. Looking back at my own code in previous projects, I realize that lazy loading was most of the time a comfortable way to ignore architecture problems. I do not wan to give up so soon – SystematicFrank Jul 19 '13 at 12:01

I think logic like this is supposed to go into repository of specific aggregate. I would question if Project and Task need to be different aggregate roots. Aggregate roots should be decided based on business needs, not on (perceived) complexity. Either way I would create either ProjectRepository::getTasks(project) or TaskRepository::getTask(project, taskId). This way, you will keep number of services to minimum while still keeping logic where it is logical to look for it.

  • I would love to manage Task objects through an aggregate with Project as root. However it's my understanding that aggregates should be pulled all at once. Whenever I see danger of an Entity pulling out too many associations, I split the aggregate. Still I want a class that given one single Project, it takes care of fetching/adding and cascading the deletions. Sounds like a repo, but ProjectRepo and TaskRepo serve other type of queries. Sounds like an aggregate, but then, aggregates should be repo agnostic. Right? Hence I try to cheat the golden rules with a Service emulating an Aggregate – SystematicFrank Jul 19 '13 at 20:06
  • mmm... just reading my previous comment I realized that ProjectRepository::getTasks(project) is not so bad. I refused the idea of a plain query like that because I was thinking in a "tree like" access ... however now I am thinking about an enhanced query object that provides such tree like access abstraction with the persistance layer. Never saw a tree result Query class, but that might fit the bill. Time to research again. Thanks for the inspiration – SystematicFrank Jul 19 '13 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.