1

I am modelling a users management system, where users have a country of residence. I have modeled both User and Country as aggregate roots, and I am referencing the user's country by its ID internally.

The storage is a relational database and I have a UserRepository and a CountryRepository. Whenever I need the user's country, I am fetching it in an application service class (using the countryId from the user).

I am trying to get a sorted list of users, based on the country name.

The option that seems most "correct" would be to have a direct object reference to the country (instead of the countryId), and then sort the users list based on the name (which would be directly accessible by the user). However, to do that, I would have to have all users in memory every time, which seems insane.

I could use the DB and make the current UserRepository fetch a sorted list from the database, joining on the country, but I feel that this would stretch the responsibilities of the repo too much - digging into the internals of the Country in the underlying storage would introduce coupling.

So far, I have managed to avoid specific read models, but am wondering whether going in that direction would be the way to do it.

Any suggestions?

  • I would just redesign your User aggregate to include the name of the country (together with its ID) as an internal element of the aggregate, and make it accessible as a read-only property via the aggregate root (with Country still being a separate aggregate). Conceptually, this is fine - your aggregates don't have to map 1-to-1 to the underlying storage. Yes, User stores a copy of Country.Name, but most of the time, you probably don't need to load Countries at all - except if you are editing the user, or are doing something with the country entity itself. – Filip Milovanović Oct 3 at 17:56
  • Also, sometimes it makes sense to give repositories querying capabilities; so you can do something like repo.GetUserPage(pageNo, sortBy). Sure, you could say that that pushes some of the business logic to the DB, but if you think about slightly differently, you can reconceptualize it as: your app works with pages, and you are just retrieving pages from storage. In any case, pushing that particular bit of logic to the DB seems low-risk. – Filip Milovanović Oct 3 at 17:56
  • BTW, I'm not suggesting anything outrageous here: if you have access to Evan's DDD book, take look at Ch 6. Aggregates - Purchase Order Integrity Example; he pretty much redesigns his PurchaseOrder aggregate in the same way (although the motivation is different). – Filip Milovanović Oct 3 at 18:01
  • BTW, BTW - "redesign your User aggregate to include the name of the country" - I mean its in-memory representation; the DB can just store the ID. Caveat: Whether or not this is the right thing to do depends on the behaviors you need to support in your actual domain, but hopefully, it gives you some idea on how to approach the modeling problem from a different viewpoint. You are not really modeling the data here, you are modeling the domain processes involved (what is done with the data, why, how it's viewed and transformed, etc.) – Filip Milovanović Oct 3 at 18:12
1

There are many things at play here, and by extension a few different approaches to relieving the tension you are describing.

The first discussion to be had is in regard to how you have modeled your domain. Obivously by changing your model, we may be able to provide more idiomatic solution to your problem. So ask yourself this, should Country be an entity at all? How often does a Country change, and what effects would such a change cause?

The first solution is to model Country as a value object. This can be accomplished in two ways: either by treating the current conceptualization as a value entirely or by partitioning your Country into two (or more) objects according to vectors of change. That is, have one country value object (CountryInfo) that holds the fields that will never change (e.g. name), and second country object (Country) that holds fields that do change (and thus behavior). This would allow any object in your domain to freely hold a reference to your country value object. I like this option the most.

The second discussion is one to which you alluded: separating the read and write models of your application. You are running up against exactly the kind of tension having a shared model can create. That is, you are beginning to consider compromising the design of your model (your business rules) in order to accommodate an orthogonal concern (likely a UI in this case). You didn't directly address this, but I doubt retrieving an ordered list of User has anything to do with a business rule. Because of this I would hesitate to include such logic in my domain.

With this in mind your second option would be to simply separate your write model from your read model. This can be as simple as allowing any ad-hoc read to be issued as necessary as a read has no chance of corrupting your domain. That is, you don't really need to model anything. Your entire read "model" can just be a database connection that returns arrays.

Your third option, again as you mentioned, is to simply provide for a query in your UserRepository that handles the semantics and ordering. There will be coupling no matter what you do because the problem you have been given requires that coupling, so I don't see much of an issue here. I find this to be a reasonable compromise for a combined read/write approach. I like this option second best.

And yet a fourth option is to simply store the Country.name along with it's id in each User entity. Again, because this value is unlikely to change, the downsides are few. That said, this option absolutely works against the ideals DDD and clean architecture would have us follow. Should your User business logic ever become dependent on a field for which it doesn't "own", your system would become susceptible to aberrant behavior. I like this option least.

  • "That said, this option absolutely works against the ideals DDD and clean architecture would have us follow." - no it does not. (It does work against widespread misconceptions about DDD and clean architecture, though.) – Filip Milovanović Oct 3 at 20:52
  • @FilipMilovanović You must be misunderstanding the implications of your position. An entity (in DDD terms) cannot depend (in terms of behavior) on data for which it is not the sole, authoritative, and unambiguous representation. It is trivial to synthesize examples where doing so results in aberrant behavior. This is the fundamental premise of DDD and, in a way, clean architecture (think managing dependencies according to vectors of change). Taken to the extreme it would mean a system absent of consistency boundaries. – king-side-slide Oct 4 at 1:28
  • Well, if you make a copy of the Country name, and if it makes sense to model the User that way, then the User aggregate doesn't depend on the Country aggregate (in this particular way); therefore User is the sole and authoritative owner, and there's a well defined consistency boundary. The fact that it may at some point in time become inconsistent with a Country isn't in itself a problem - as long as eventual consistency is acceptable. It makes no sense to talk about what's aberrant behavior in general terms, as it depends on the domain. Aggregates are not to be designed like database tables. – Filip Milovanović Oct 4 at 1:57
  • there are some interesting thoughts in this answer. However, considering Country as a value object would completely miss the point of Country objects having an identity. If OP modelled it as as an aggregate root, it is certainly because other attributes are associated to it. In many business applications, the country is not just a value in an address but a critical information that determines rules to be applied, legal constraints, currencies, localisation settings, responsible actors and so on. Furthermore the question is general enough to apply to any other relevant aggregate (e.g. company?) – Christophe Oct 4 at 6:05
  • @FilipMilovanović There are many things to unpack here. First is that an entity clearly cannot be considered the "owner" of some piece of data if it is not responsible for mutation of that data (i.e source of truth). In this way, copying Country.name into User does not make the User its "owner". Second, asking to allow for eventual consistency (in any system) is a consideration not to be taken lightly! Not only does it add quite a bit more complexity, there are simply some problems where EC is untenable. – king-side-slide Oct 4 at 14:34
1

Guiding principle

Your DDD model shall reflect the reality of the domain, independently of chosen the technology.

Note: You may chose the technology that best copes with the domain model, but you should not twist your design model for addressing implementation issues.

Design constraint

The DDD model and the usual architectural patterns should stay independent of the underlying database layer. This means that the same DDD-based design should hypothetically allow to change the persistence layer in the future.

Note: beyond DDD, all modern architectural principles such as the hexagonal architecture, the onion architecture, the clean architecture promote this principle.

Consequences

You should therefore design your application logic on the base of your domain model, without building on database-specific features. But the data source layer should still allow to make best use of the underlying technology:

  • With an RDBMS, the most efficient solution of your sorting is only one JOIN away. But the repository pattern does not allow for different aggregates to be mixed, thus preventing the join.
  • With a document store you would probably have to fetch two different documents exactly as you have described. But some document layout could facilitate navigation between related documents. And some querying tactics could optimise a particular sorting. In your specific example, knowing that Country is a much smaller collection than User, the query could iterated through a sorted set of Countries, and then query for the users in each country so that the sort is naturally done.

The key is to offer an API that fits the requirements but isolates the need for DB specific optimisations in the appropriate layer.

How ?

Building brute-force merging of entities into your application logic and do yourself what the database layer could do better is not a good solution.

The following tactics may get a more suitable and general solution:

  • Enrich your repositories to give you the kind of extended sort that you need. The repository implementation could the use its knowledge of the underlying database to optimise. Inconvenience: it might get very painful to later migrate from RDBMS to document store, but now you’d get the full power of your RDBMS.
  • If the sorting need is for only for specific reporting and not a general need, you could consider using query objects in order to avoid polluting your repository.
  • You could use a “tactical model”: You would not change your DDD model, but you’d consider the country id and name as a value objects, only for the purpose of implementing the User repository: you'd nevertheless manage Country in the rest of the application (and in the db) as the true entity it is. (like having a view in an RDBMS)
  • If you have many more of these needs, you could generalise the principle of the "tactical model" with CQRS (the query model being a kind of tactical model)
  • An interesting point made here. If your "DDD" design prevents you from using your particular storage technology properly, then there is something seriously wrong with it. Just take a step back and listen to how crazy this sounds! You are implementing software, choosing a proper database for your use-case, which you then can not really use because of the design you also chose. – Robert Bräutigam Oct 5 at 13:14
  • @RobertBräutigam I've tried to make the opposite point: my key recommendation is to keep the design model consistent with the reality, independently of technology used. If the domain requires a country to be an aggregate root, then it makes no sense to break a good model, just because some minor sorting issues (that do no fit in the OO patterns chosen, but that could easily be handled in an RDBMS). My point is therefore to find the right place to address the sorting tactics need without breaking the strategic design (and isolate use of DB layer capabilities from the application layer) – Christophe Oct 5 at 13:38
  • @RobertBräutigam In view of your remark, I've edited my answer so that the points raised in my comment are put forward in a clearer way (I hope). – Christophe Oct 5 at 14:35
  • I think I understood you correctly, I fundamentally disagree though. The quote: "...makes no sense to break a good model, just because some minor sorting issues"... highlights my issue with this "persistence agnosticism" idea. The model is not good by definition if it can't cope with requirements. In fact, there are only requirements. There is no model, no objects, no "real world" without requirements. – Robert Bräutigam Oct 5 at 16:45
  • 1
    @RobertBräutigam Hmm... you seem to be begging the question. I agree that the model should cover the whole spectrum of requirements, but the topic at hand is how to model a system where the requirements are seemingly at odds with one another in terms of design. Users.DisplayTable() is about as useful an answer to this question as ChessGame.CalculateBestMove() is to a question about how to model chess. There is no information about how either of the above is modeled. Would your above model output the correct order of users if Country.ChangeName() was called on line before? If so, how? – king-side-slide Oct 8 at 16:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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