0

How to make entities lazy load its relationships?

For example: Post and Comment models, where a Post can have 0 or more Comments. How to make the getComments() method on Post entity lazy load its Comments?

My first think, is to have an CommentRepository injected into my Post entity, how is this bad? Since Entities and Repositories are part of may domain, why can't they have a two way knowledge about each other?

Thank you

UPDATE
I know there are many excellent industry standard ORMs that perform lazy loading for the main languages out there, but I don't want to rely on its magics. I'm looking for a ORM/DBAL agnostic solution to make sure of the application's low coupling.

Leo Cavalcante
  • 2,327
  • 2
  • 22
  • 30

3 Answers3

2

Aggregates represent a consistency boundary so there should never be a need to lazy-load related data as the aggregate as a whole should always be consistent. All objects that belong to an aggregate have no need to exist on their own. If you do have an object that has it's own life-cycle then it needs to be removed from the aggregate.

When you do find that you need to do this you may want to rethink your design. It may be that you are using your object model to query. You should rather have a light-weight query model that can perform this function.

Injecting repositories or services into entities is generally not the best idea. A double-dispatch mechanism should be preferred.

But in your case I would still try to not lazy-load.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • 1
    Agree; additionally `Post` shouldn't hold objects references to `Comment`, only Ids. See, e.g., the highest votest answer in http://stackoverflow.com/questions/4919687/aggregate-root-references-other-aggregate-roots – Alexander Langer Sep 02 '14 at 06:04
  • So I should not have a getComments() method on my Post entity? – Leo Cavalcante Sep 03 '14 at 01:36
  • 1
    Well, I would not have a `getComments()` method on `Post` from what I can tell. If you *do* have one then rather use double-dispatch so that you do not inject the repository: `Post.getComments(ICommentRepository)`. The question you need to ask is why you need the comments on the `Post`. If only for querying then a separate `ICommentQuery` implementation that sends back the most basic structure may suffice. Although once one has a repository then it implies an entity. If `Comment` is not an entity that belongs to another then the comments all need to be loaded with the aggregate anyway. – Eben Roux Sep 03 '14 at 04:09
1

Consider using a proxy that subclasses Post, overrides the getComments() method. Inject the proxy with the CommentRepository and access it in the overridden getComment() method.

This is how an ORM would typically do it. It keeps your domain classes clean as only the proxy is dependent on a data access mechanism.

David Osborne
  • 6,436
  • 1
  • 21
  • 35
  • And what will be the difference between proxies and entities? For each entity I will have a proxy, so why maintain both? – Leo Cavalcante Sep 03 '14 at 01:39
  • 1
    Because you can swap a proxy, and its data access technology, without affecting the entity. A well designed entity/domain model must be agnostic of these matters, IMHO. Furthermore, you could change the strategy that the proxies use to perform lazy loading and essentially plug-in the most appropriate. Again, all without affecting the core model. SRP & OCP compliant. Tidy. – David Osborne Sep 03 '14 at 07:31
0

At first, you should separate domain concept from details of realization. Agreagate pattern is about how to organize your domain and lazy-loading is an implementation detail.

Also, I disagree with @Eben Roux about inconsistency of agreates. Lazy loading contradicts nothing in my opinion. I express why.

Lazy loading itself

To understand how lazy loading can be implemented you may refer to Martin Fowler's PoEAAA pattern 'Lazy loading'. For me, proxy pattern is the best solution. Also, it's important that most nowadays ORMs supports lazy loading, BUT for data model (not domain model).

It is a good practice to separate data model and domain model and use repostiories to hide this transformation:

Separated domain and data models

In this case objects of domain model are constructed inside repositories those hide ORM context. Required data object and all associations are loaded by ORM, than transformation to domain model is performed, and finally, constructed domain object returned.

The question is how to load some associations not during creation of domain object, but during it's lifetime. You can use Repoisotry inside entity and I see nothing wrong with it. It will looks like:

public class Post {

    private ICommentsRepository _commentsRepository;

    private IList<Comments> _comments;

    //necessary to perform lazy loading (repository always wroks with ids)
    private IList<int> _commentIds;

    //realize lazy loading
    ...
}

there are problems:

  1. Your model now becomes not clear. It contains 'techincal' information like _commentIds.
  2. As soon as you want to define ICommentsRepository you claim the Comment to be aggregate root. If we introduce agregate pattern into domain model, repositories should be creaed just for agregate roots. Thus it means that Comment and Post are different agregate roots. And possible that it is not what you want.

There is better solution:

public interface ICommentList {
...
}

public class CommentList : ICommentList {
...
}

public class CommentListProxy : ICommentList {

   private CommentList _realCommentList;

   private IList<int> _commentIds;

   //realize lazy loading here using ORMs capabilities! 
   //don't use repository here!

}

public class Post {

   private ICommentList _commentList;

   ...
}

Post repository will initaize _commentList field with proxy object. Also, it is necessary to say:

  1. CommentListProxy relates to data model layer, not to domain model. It uses ORMs capabilities to implement lazy loading
  2. and thus doesn't use repositories, and thus you may consider CommentList as a part of the Post agregate.

The only possible disadvantage of this approach is in implicit database querying when operating with domain objects. This must be clear for users of the Post class.

Smart ORMs

Finally there are kind of ORMs which allows you to use same model for both domain and data. It realizes lazy-loading for domain model in a same way as for data model. Take a look at DataObjects.Net. For some cases it is a good solution.

Valentin P.
  • 1,131
  • 9
  • 18
  • Why I need to have commentIds? The getComments() method on Post entity will just delegate the call to the injected CommentRepository, I do not see why I need to store de IDs of the related comments... – Leo Cavalcante Sep 03 '14 at 01:44
  • Ok, you tell about ORM-agnostic solution, so what params do you want to pass to repository to get comments? If you are holding data instance in your domain instance - it is a bad practice. Also, I've just explained you why the creation of repository for non-agregate root entity (I consider Comment) is bad: "As soon as you want to define ICommentsRepository you claim the Comment to be aggregate root. If we introduce agregate pattern into domain model, repositories should be creaed just for agregate roots. Thus it means that Comment and Post are different agregate roots..." – Valentin P. Sep 03 '14 at 08:31
  • I think second solution (with CommentListProxy) is best one. If you want, you may hold data (!) instance of Post in CommentListProxy instead of Comments ids. It is possible, because CommentListProxy relates to data layer (!), not domain. And thus, it can operate ORM capabilities to lazy load all comments. – Valentin P. Sep 03 '14 at 08:34