DDD - How Can I Avoid Crossing Aggregate Boundaries Here?
Asked Answered
C

3

7

We're working on a new project (re-writing existing app), and I'm running into problems with my domain model / repository design.

Here is a (simplified) version of two key portions in our domain model:

alt text

As you can see, I have an abstract concept of a Post, which can be things like a Review, a Discussion, a Photo, a Video, etc. Posts can also have comments.

I also have a abstract concept of a Location, which are obviously things like Streets, Cities, Neighbourhoods, etc.

Now, this naturally looked to me as two clear aggregate roots.

So I created two repositories, one called PostRepository, and another called LocationRepository.

This was all working fine, I can add/get any type of Post (or comment), and add/get any type of Location via one of these two repositories.

But now im in the scenario of a "landing page" for a City (for example).

On this page, I need to basically show "all posts for this location".

How is that defined? Well, a Post can be (optionally) tagged at a Location. Implementation detail, so I don't want to go too deep into data (as that's not what DDD is about), but essentially there is geospatial intelligence to work out which posts are contained in a particular location by the shape file of the location, and the latitude/longitude of the tagged Post.

But how can I retrieve this information without crossing the boundaries?

Which repository do I use? Do I need a new one?

If it matters (or for the curious), this is a web application (ASP.NET MVC), with a SQL Server 2008 database and Entity Framework 4.0.

If you need any clarification, let me know.

EDIT

We currently use a modified version of the Specification pattern in order to retrieve domain models.

For example, this is the code in our BLL to retrieve all Review's where Score >= 4:

var reviews = postRepository // GenericRepository<Post>
      .Find() // IQueryable<Post>
      .OfType<Review>() // IQueryable<Review>
      .Where(x => x.Score >= 4)
      .ToList(); // List<Review>

But now I need some code like this:

var reviews = postRepository
    .Find()
    .OfType<Review>()
    .Where( //lat long, or Locations FK )
    .ToList();

The problem is I don't know how to do the above query without adding an intermediary join-entity (LocationPost - as it's a many to many), and add a FK to the Post domain model to that.

But by doing that, I am crossing the aggregate boundaries - aren't I?

Convexoconvex answered 26/11, 2010 at 1:29 Comment(0)
C
6

I would bind post to the location at creation time so that for each location I can get (through a repository) a list of associated posts. It would look like this:

Creation:

var p = new Post(latitude, longitude);
var locations = locationRepository.FindByCoordinates(latitude, longitude);
foreach (var l in locations)
{
    l.AssociatePost(p);
}
session.Save(p);

Retrieval:

var associatedPosts = postRepository.FindByLocation(locationId);
foreach (var p in associatedPosts)
{
    Display(p);
}

Under the hood, the association between posts and location would be implemented as a many-to-many table relationship. There is one problem with this solution: adding a new location requires to scan all the posts and assign them to the new location (if applicable).

Hope that helps.

Crumpton answered 26/11, 2010 at 5:30 Comment(3)
+1, i like this. I'm going to try this out when i get in the office and i'll let you know how i get on. Cheers. :)Convexoconvex
I have gone with a modified version of this solution. Your overall solution put me in the right direction, cheers.Convexoconvex
+1 - Good answer. To the OP, even though it is a little late, your model fits into the classic "discussion forum" format, if you would think of a location being the "forum" and the post being itself. You will find this to be a very common pattern when dealing with aggregate boundaries and both one-to-many and many-to-many relationships.Buckjump
F
10

Why is this a problem? According to Evans in his book, one AR may very well reference another AR. (You may not however reference a child element in an AR from another AR)

Also, are locations really aggregate roots? The definition of an aggregate root is that it acts as a boundary of concistency. Does that fit the definition of a location? I'd say a location is a value object.

There are pretty much two camps here regarding repositories and AR associations:

One that says that all aggregate roots have to be fetched through their respective repository, and AR's should use soft relations, eg ID's between them

And one that says that aggregate roots may very well fetch other associated aggregate roots and that a repository is merely a way to find aggregate roots.

Fame answered 26/11, 2010 at 9:36 Comment(1)
Hmm, interested point RE value object. But Locations all do have an identity (LocationId), and also they have a latitude/longitude, which is also unique. But then again they are immutable, so maybe they are value objects.Convexoconvex
C
6

I would bind post to the location at creation time so that for each location I can get (through a repository) a list of associated posts. It would look like this:

Creation:

var p = new Post(latitude, longitude);
var locations = locationRepository.FindByCoordinates(latitude, longitude);
foreach (var l in locations)
{
    l.AssociatePost(p);
}
session.Save(p);

Retrieval:

var associatedPosts = postRepository.FindByLocation(locationId);
foreach (var p in associatedPosts)
{
    Display(p);
}

Under the hood, the association between posts and location would be implemented as a many-to-many table relationship. There is one problem with this solution: adding a new location requires to scan all the posts and assign them to the new location (if applicable).

Hope that helps.

Crumpton answered 26/11, 2010 at 5:30 Comment(3)
+1, i like this. I'm going to try this out when i get in the office and i'll let you know how i get on. Cheers. :)Convexoconvex
I have gone with a modified version of this solution. Your overall solution put me in the right direction, cheers.Convexoconvex
+1 - Good answer. To the OP, even though it is a little late, your model fits into the classic "discussion forum" format, if you would think of a location being the "forum" and the post being itself. You will find this to be a very common pattern when dealing with aggregate boundaries and both one-to-many and many-to-many relationships.Buckjump
E
0

Let's say you used the Specification pattern, could you build a Post Specification using a Location object? Then you just pass the Specification to your Post Repository, and get back the result.

Evangelina answered 26/11, 2010 at 3:44 Comment(8)
Hmm, never heard of the "Criteria Pattern". Or are you referring to the Specification pattern? (we use a modified version of that - via C# Expressions)Convexoconvex
@Convexoconvex could You post that "modified specification pattern" approach on pastebin/gist and drop it's link here? I smell something interesting.Allocation
@RPM1984: That's why I couldn't find it on google! :) Yep, that's what I meant.Evangelina
@Arnis L. - good to see you, shall we have another heated DDD discussion? Just kidding. :) Actually i just watched a NDC video on 7 reasons why DDD projects fail and it blew my mind. Not much to that code, i didn't "find" it anywhere - public ICollection<T> FindAll<T>(Expression<Func<T,bool>> predicate) : where T : PostConvexoconvex
That is a method on the domain service, (aka PostServices) that can be used like this: var reviews = _postService.FindAll<Review>(x => x.ReviewScore >= 5);. The implementation acts on a GenericRepository<T>, using the predicate on the .Where.Convexoconvex
@Convexoconvex You know already - I dislike this approach. Btw, check out what CQRS is all about. That will blow Your mind even more. Just "don't try that at home" (on a real project) unless You feel really comfortable with it. :) bit.ly/3H2WaQAllocation
@Arnis L - thanks for the links, yeah i've heard about it, but honestly it's pretty full on. I think our project falls into the "DDD-Lite" category. We don't really want to be segregating our architecture into queries/commands, when it's such a straightforward app in the first place. I could see the benefit in other architectures though (N-Tier for example).Convexoconvex
@Convexoconvex About "DDD-Lite" I think You are right. @ work, guys next to me doing exactly the same. But I do think it's better than "static web pattern". bit.ly/6fw1Ak About cqrs, thing is - that's a really good mind exercise. When You understand complex things, less complex ones becomes simple.Allocation

© 2022 - 2024 — McMap. All rights reserved.