How to model sort order for many-to-one across two aggreagate roots
Asked Answered
B

2

8

Take the domain proposed in Effective Aggregate Design of a Product which has multiple Releases. In this article, Vaughn arrives at the conclusion that both the Product and Release should each be their own aggregate roots.

Now suppose that we add a feature

  • As a release manager I would like to be able to sort releases so that I can create timelines for rolling out larger epics to our users

I'm not a PM with a specific need but it seems reasonable that they would want the ability to sort releases in the UI.

I'm not exactly sure how this should work. Its natural for each Release to have an order property but re-ordering would involve changing multiple aggregates on the same transaction. On the other hand, if that information is stored in the Product aggregate you have to have a method like product.setRelaseOrder(ReleaseId[]) which seems like a weird bit of data to store at a completely different place than Releases. Worse, adding a release would again involve modification on two different aggregates! What else can we do? ProductReleaseSortOrder can be its own aggregate, but that sounds downright absurd!

So what to do? At the moment I'm still leaning toward the let-product-manage-it option but what's correct here?

Blondie answered 28/7, 2014 at 18:0 Comment(0)
M
3

So, Product and Release are both ARs. Release has an association to Product via AggregateId. You want to get list of all releasesfor a given product ordered by something?

Since ordering is an attribute of aggregate, then it should be set on Product, but Releases are ARs too and you shouldn't access repository of Release in Product AR (every AR should have its own repository).

I would simply make a ReleaseQueryService that takes productId and order parameter and call ReleaseRepository.loadOrderedReleasesForProduct(productId, order).

I would also think about separating contexts, maybe model for release presentation should be in another context? In example additional AR ProductReleases that would be used only for querying.

Mite answered 12/8, 2014 at 10:34 Comment(4)
Using that strategy it would seem that something like this will generate a better ubiquitous language releases.loadOrderedForProduct(product). I have had it suggested that maybe this implies that OrderedProductReleases might be a different ARBlondie
I'm avoiding using repositories inside ARs, because it's not AR responsibility to persist data (it violates single responsibility principle SRP from SOLID), also it makes ARs domain logic harder to unittest. So releases.loadOrderedForProduct(product) would be wrong imo. Try with a different AR OrderedProductReleases and create another repository for this AR.Ronnie
I think you misunderstand me (my fault since I changed your convention). releases is an instance of ReleasesRepository, I simply call the instance variable releases since a repository represents a collection of entities.Blondie
ah, then it should be okRonnie
H
4

I have found that in fact it is best to create a new aggregate root (e.g., ProductReleaseSorting as suggested) for each individual sorting and/or ordering purposes. This is because releaseOrder clearly is not actually a property of the Product, i.e., something that has a meaning on a product on its own. Rather, it is actually a property of a "view" on a collection of products, and this view should be modeled on its own.

The reason why I tend to introduce a new aggregate root for each individual view on a collection of items becomes clear if you think of what happens if you were to introduce additional orderings in the future, say a "marketing order", or multiple product managers want to keep their own ordering etc. Here, one easily sees that "marketing order" and "release order" are two different concepts that should be treated independently, and if multiple persons want to order the products with the same key, but using different orderings, you'll need individual "per person views". Furthermore, it could be that there are multiple order criteria that one would like to take into account when sorting (an example for the latter would be (in a different context) fastest route vs. shortest route), all of which depends on the view you have on the collection, and not on individual properties of its items.

If you now handle the Product Manager's sorting in a ProductReleaseSorting aggregate, you

  1. have a single source of truth support for the ordering (the AR),
  2. the ProductReleaseSorting AR can enforce constraints such as that no two products have the same order number, and you
  3. don't face the issue of having to update multiple ARs in a single transaction when changing the order.

Note that your ProductReleaseSorting aggregate most probably has a unique identity ("Singleton") in your domain, i.e., all product managers share the same sorting. If however all team members would like to have their own ProductReleaseSorting, it's trivial to support this by giving the ProductReleaseSorting a corresponding ID. Similarly, a more generic ProductSorting can be fetched by a per-team ID (marketing vs. product management) from the repository. All of this is easy with a new, separate aggregate root for ordering purposes, but hard if you add properties to the underlying items/entities.

Halo answered 15/8, 2014 at 13:36 Comment(2)
Thanks for the answer, it's not what I ended up doing in my case as my needs were more aligned with the other answer, but this is definitely a perfectly legitimate option. One thing though, I probably wouldn't go this approach if sort order was only a view concern until multiple user support was actually necessary. However, if sort order was something that impacted business logic I would absolutely make it its own AR.Blondie
@GeorgeMauer have you come to understand this problem better in the years since? I am struggling with a similar problem, where I have a User AR and a Country AR and I have to sort users by their country's name. I am currently leaning towards modelling it as a separate service.Ophir
M
3

So, Product and Release are both ARs. Release has an association to Product via AggregateId. You want to get list of all releasesfor a given product ordered by something?

Since ordering is an attribute of aggregate, then it should be set on Product, but Releases are ARs too and you shouldn't access repository of Release in Product AR (every AR should have its own repository).

I would simply make a ReleaseQueryService that takes productId and order parameter and call ReleaseRepository.loadOrderedReleasesForProduct(productId, order).

I would also think about separating contexts, maybe model for release presentation should be in another context? In example additional AR ProductReleases that would be used only for querying.

Mite answered 12/8, 2014 at 10:34 Comment(4)
Using that strategy it would seem that something like this will generate a better ubiquitous language releases.loadOrderedForProduct(product). I have had it suggested that maybe this implies that OrderedProductReleases might be a different ARBlondie
I'm avoiding using repositories inside ARs, because it's not AR responsibility to persist data (it violates single responsibility principle SRP from SOLID), also it makes ARs domain logic harder to unittest. So releases.loadOrderedForProduct(product) would be wrong imo. Try with a different AR OrderedProductReleases and create another repository for this AR.Ronnie
I think you misunderstand me (my fault since I changed your convention). releases is an instance of ReleasesRepository, I simply call the instance variable releases since a repository represents a collection of entities.Blondie
ah, then it should be okRonnie

© 2022 - 2024 — McMap. All rights reserved.