In the previous email, I elaborated on the article about domain model purity and lazy loading.
Just to re-iterate, here’s the sample code:
public class User : Entity { private List<LoginSession> _loginSessions; public virtual IReadOnlyList<LoginSession> LoginSessions => _loginSessions.ToList(); public virtual void RegisterSession(DateTime now) { LoginSession session = _loginSessions.Last(); if (session.HappenedRecently(now)) session.Update(now); else _loginSessions.Add(new LoginSession(this, now)); } }
User
and LoginSession
are part of the domain model; User
here relates to LoginSession
as 1-to-many.
The User
class here is pure even though an ORM (such an NHibernate or EF Core) creates a proxy class on top of it and overrides the LoginSessions
property such that the collection of login sessions is loaded lazily from the database.
Here’s a picture that provides a mental model about lazy loading and domain model purity:
In short: it’s the UserProxy
class that reaches out to the database, not User
. Therefore, User
itself remain pure.
This is the typical hexagonal architecture at play: the app services layer (the proxy class) depends on the domain layer (the User
class), but the opposite isn’t true.
I received an interesting follow-up question that I thought I would share with you:
As we know, every Aggregate Root manages the consistency of all the Aggregate’s contents, including Entity objects like
User
in your example. Let’s assume the Aggregate Root now wants to create a new user, isn’t there a problem because an instance of theUserProxy
must be created instead of anUser
(so that it can reach the database)?
It’s a great question because it shows the beauty of lazy loading and its implementation in ORMs:
You don’t need to create proxy classes on top of newly created entities.
The only reason why ORMs create proxy classes is to load additional data from the database during the business transaction (if needed). Since the domain object is new, there’s nothing to load from the database — the object hasn’t been persisted yet.
Therefore, all you need to do is instantiate the User
as usual, without registering it with the ORM. It’s only when you load that object from the database, do you need the proxy. But since you are doing the loading via the ORM, that scenario is already covered — the ORM will create the proxy for you!
This is how the two scenarios would play out:
Notice how in the first scenario, the controller doesn’t need a proxy in order to save the user to the database.
Also notice how in the second scenario, the controller calls ReadDetails()
on the user proxy, and this call gets split internally: part of it goes to the database to retrieve all the login sessions, and part is delegated to the underlying User
domain class, since some of the user’s data (e.g email and name) was loaded eagerly, along with the user itself.
Once again, the User
domain class doesn’t know anything about its proxy class and doesn’t depend on it in any shape and form. Should you disable lazy loading, the domain class would work just as before.
Enjoy this message? Here are more things you might like:
Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.
Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »
My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »