8

Say I have the concept of a User; a very basic structure in reality. If the business requires that an aspect of the software needs to display all related 'Posts' for example, how should that be modelled?

  1. Should there be posts as an attribute on the User entity, meaning that every time a User is required simply to verify a login for example, all it's posts must be loaded?
  2. A separate aggregate entirely should be used. What should this aggregate be called? UserWithPosts? What if there are more attributes for related data?
  3. The aggregate root should be an interface User, with a simple Entity called LoginUser, and decorators for extra functionality, like UserWithPosts, UserWithComments etc?

I can't figure out which would be the better approach to avoid scenario 1.

designermonkey
  • 383
  • 4
  • 11
  • Are you using Hibernate or something similar? IMO, it's harmful to think in such inflicted OO-way. I suggest implementing data-access logic using services which perform specific and optimal queries ([jOOQ](https://www.jooq.org/)-style) and return results in a form of DTOs or value objects. – scriptin Jul 28 '17 at 14:14
  • Your aggregates are generally not used to populate views. You would use plain old SQL for that. – Andy Jul 28 '17 at 15:49

3 Answers3

5

There seems to be a confusion between domain driven design concepts, API design and implementation approach.

Domain design concepts

First a few definitions of Evan's DDD reference book:

  • Aggregate: A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the aggregate, designated as the root.

  • Entity: An object fundamentally defined not by its attributes, but by a thread of continuity and identity.

And keep in mind that this is about the domain. We don't care here how the relationships between the objects will be implemented. Therefore, from what you say:

  • User and related Posts belong to the same aggregate, and User is the root entity thereof. This says that whenever I want to refer to a Post, however it is implemented, I have to access it via the User.
  • it's not clear if Post within the aggregate is an entity or a value object. The difference is about identity semantics: is a post that is updated (e.g. correction of a typo) still the same post or would if be considered as another post ? Personally I'd opt for the entity, but up to you to see what fits best your domain.

API design & implementation model

The domain model is independent of any specific implementation. But in order to implement it, you'll have to define:

  • An API, that defines how the domain is exposed to the application layers that use the model (e.g. controllers and views).
  • An implementation approach for managing the implementation dependent internals of the model, for example to ensure the persistence (e.g. use of an Object-Relational mapping or use of a NOSQL database).

The domain model shall not be a prison that imposes you to blindly do things that hamper performance, but a guide to structure better your software.

In your specific case, you could for example use lazy loading: your API may give the impression that everything is loaded at once with the User, but in reality, the posts will not be loaded until someone tries to access it via the User (thanks to encapsulation).

But you could also define an API that seems to give direct access to the entities in the aggregate, but which enforce the access to Post to go via User, so that you have the opportunity to safeguard the consistency of the related objects.

You could also, as you suggest yourself, create different kind of User objects in your application to determine what is to be loaded and what could be changed. But then it's an implementation model and no longer the conceptual domain model anymore.

Christophe
  • 74,672
  • 10
  • 115
  • 187
4

There are two parts to this.

For use cases that don't modify the data, you don't need to load the entire aggregate into memory. So you might use one read-only view of User to do login, and a different read-only view to show Posts.

What that would look like is to different methods on the repository, each returning a read-only view that is fit for the purpose.

Use cases that modify an aggregate need all of the state required to maintain the invariant; but the use cases to view an aggregate do not.

You won't find this described in the Evans book -- it's a technique that has become more popular since then.

The second part of this is the modeling question - are the collection of Posts associated with a User, or are they an aggregate unto themselves? That's really going to depend upon the invariant that you are trying to maintain.

It's normally acceptable to have relationships between aggregates, where one aggregate's state includes the identifier of another aggregate.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
  • Yeah, the relationship is a collection of `PostId` objects, so I guess that relieves the second part's concerns. So, I guess that as I know what the operations on the aggregate will be, I can indeed have immutable classes representing the read state I need. I also guess I could have a repository method like `getUserForDetailsChange` that would skip loading the `PostId` collection as it knows it's use case won't include it? – designermonkey Jul 28 '17 at 16:37
3

For (1), I would say no way! You don't want to read a bunch of database objects every time you fetch a user, especially considering you don't always need the other database objects. The way (1) is the root cause of many performance problems. My understanding is that Hibernate makes it all too easy to choose way (1), and that is one of the reasons I prefer writing SQL queries manually.

My approach is (2). I create different immutable "views" (not database views!) to the same base object. There can be as many as 3-5 views that join the base database table to different other tables. These view objects are never modified; all database modifications go via the base object.

I usually try to keep all logic in the application itself, so even though I have these application-level views, I never actually use the CREATE VIEW SQL command. Frankly, I don't see the purpose of database-level views. They do something that should be done in the application.

If the programming language you use is Java, you could have a class User and an inner class User.WithPosts. The inner classes are so trivial that it makes sense to have them as inner classes, and it also makes it easy to find where the relevant views are, if you follow the habit of making them inner classes. I make these inner classes static so you can have User.WithPosts without User.

juhist
  • 2,579
  • 10
  • 14
  • So your view objects are the inner classes? I would struggle there as I am using PHP. Would you suggest using separate aggregates for them in this case? Or all under the same aggregate? – designermonkey Jul 28 '17 at 16:29
  • Ach, PHP. I haven't used it for 15 years, but I guess it supports object oriented programming, so do create view classes using whatever mechanisms you have. I'm not entirely sure what you mean with the word "aggregate", perhaps somebody with more PHP knowledge can shed a light on that. – juhist Jul 28 '17 at 16:39
  • I mean a domain aggregate. So, a separate entity, repository etc. – designermonkey Jul 28 '17 at 16:44
  • Maybe this could help me: https://stackoverflow.com/a/34598986/1940255 – designermonkey Jul 28 '17 at 16:46
  • @designermonkey I didn't say you absolutely need to have inner classes. It is just a handy naming convention with a pretty dot in it. If PHP doesn't support inner classes easily, just name it UserWithPosts. – juhist Jul 28 '17 at 16:53
  • I use similar way to model a application. Hibernate make it too easy for developers to thinking about boundaries, encapsulation and performance. Entities (the base object) should be managed by ORM but should not directly exposed to application business logic layer. Intead, we should carefully plan the Aggregate Root based on these Entities which only provide the necessary information and operation for the specific business case. – ehe888 Jul 23 '21 at 21:29