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, 2013 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 Jul 19, 2013 at 12:01

1 Answer 1


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 Jul 19, 2013 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 Jul 19, 2013 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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