0

I do not use lazy loading. My root aggregate have entities (collection navigation properties). I want my aggregate to be self-contained, responsible for itself, and follow the Single Responsibility Principle (SRP), and adhere to high cohesion and low coupling.

The problem is that the code that retrieves the root aggregate needs to include certain child entities depending on which way it wants to interact with the aggregate.

Example:

public class Blog // My root aggregate
{
    public ICollection<Author> Authors { get; set; }
    public ICollection<Post> Posts { get; set; }

    public AddAuthor(Author author)
    {
        _authors.Add(author);
    }

    public AddPost(Post post)
    {
        _posts.Add(post);
    }
}

If I want to add a author, I have to do:

var blog = _context.Blogs.Include(x => x.Authors).Single(x => x.BlogId == 1);
blog.AddAuthor(/* ... */);

And if I want to add a post, I would have to do:

var blog = _context.Blogs.Include(x => x.Posts).Single(x => x.BlogId == 1);
blog.AddPost(/* ... */);

But I feel this breaks encapsulation, because now my Blog aggregate is not self-contained, its functionality depends on how the caller has retrieved the aggregate from the DbContext (or the repository). If the caller did not include the necessary dependent entities then the operation on the aggregate would fail (since the property would be null).

I would like to avoid lazy loading because it is less suitable for web applications and performs worse due to executing multiple queries. I feel that having a repository with methods such as GetBlogWithAuthors and GetBlogWithPosts would be ugly. Do I have to create a repository method such as GetBlog which always include all child entities? (this would a big, slow query, that would timeout).

Are there any solutions to this problem?

plr108
  • 1,201
  • 11
  • 16
Fred
  • 12,086
  • 7
  • 60
  • 83
  • An Aggregate is a consistency boundary. As such, it should contain all of the data required to maintain consistency via domain rules. Have you considered vertically partitioning your aggregate and creating two distinct Aggregates with two different repositories? – CPerson Dec 04 '19 at 14:16
  • No, I have not, but maybe I should. But isn't it common for an aggregate to have more than one child entity? And a user only interacting with the aggregate in a what that only uses one of the child entities? Also I feel a blog contains both authors and posts and hence should be one aggregate. This was just an example though. I don't know any partitioning strategy. – Fred Dec 04 '19 at 14:47
  • I was proposing a solution to your problem. Whether your Aggregate should be split up depends on your use cases. You described scenarios where Authors and Posts would never be required at the same time and further you didn't want to load both for every use case. If you either have a scenario where both are required or you are OK with loading the full Aggregate Root, then partitioning is not the way to go. HTH – CPerson Dec 04 '19 at 19:27
  • Well both authors and posts are part of what constitutes a blog, so they are conceptually related and part of the same domain (DDD). They belong together. This doesn't mean that I always want to load both, since I might just want to manipulate one of the entities. – Fred Dec 04 '19 at 20:47

1 Answers1

2

I realize it is probably a practice domain but I think an important point that is not talked about enough is that strict DDD should not always be applied. DDD brings a certain amount of complexity to minimize the explosion of complexity. If there is little complexity to start with, it is not worth the added upfront complexity.

As was mentioned in the comments, and Aggregate is a consistency boundary. Since there does not seem to be any consistency being enforced, you can split it. Blog can have a collection of PostRef or something so it need not pull back ALL Post data where PostRef has maybe Id and Title?

Then Post is its own aggregate. I am guessing that Post has an Author. It is recommended not to reference entities in other aggregates that are not the aggregate root so now it seems like Authors should not be in Blog.

When your starting point is an ORM, my experience is that your model will fight the DDD recommendations. Create your model and then see how to persist your aggregates. My and many other's experiences at that point is that an ORM just isn't worth the yak shaving that it brings throughout the project. It is also far too easy for someone who does not understand the constraints to add a reference that should not be there.

To address performance concerns. Remember that your read and write models do not have to be the same. You optimize your write model for enforcing constraints. If separate you can then optimize your read model for query performance. If this sounds like CQRS to you, then you are correct. Again though, the number of moving parts increases and it should solve more problems than it introduces. Again, your ORM will fight you on this.

Lastly, if you do have consistency constraints that require really large amounts of data, you need to ask the question of whether they really need to be enforced in real-time? When you start modeling time, some new options emerge. SubmittedPost -> RejectedPost OR AcceptedPost -> PublishedPost. If this happens as a background process, the amount of data that needs to be pulled will not affect UX. If this sounds interesting I suggest you take a look at the great book Domain Modeling made Functional.

Some other resources:

Devon Burriss
  • 2,497
  • 1
  • 19
  • 21