Lazy load and persistence ignorance
By Jérémie Chassaing on Saturday, February 7, 2009, 14:44 - Domain Driven Design - Permalink
I often see questions about how to make lazy loads in entities, and wether using dependency injection for it.
The usual response is something like this :
class Entity
{
private Guid id;
private SubEntity subEntity;
private IDataService dataService;
public Entity(Guid id, IDataService dataService)
{
this.id = id;
this.dataService = dataService;
}
public SubEntity SubEntity
{
get
{
if (subEntity == null)
subEntity = dataService.GetSubEntity(id);
return subEntity;
}
}
}
As you can see, your entity is seriously tied to your persistence problem now. Even if it accesses the persistence layer through an interface, it is not persistence ignorant anymore.
The smell is that you cannot create an in memory instance of your entity anymore without worrying about data access.
Should you sill inject the data access service, or is it useless and you should leave it null ?
So believe me : do not inject data services in your entity.
Now, how can we still have the lazy load behavior ?
Through injection. But not dependency injection, I use execution inversion of control through delegate injection.
If you give to your entity a function that will return the value when asked, it's as if you gave the value.
Let's encapsulate this in a small class :
public class Lazy<T>
{
private T value;
private Func<T> loader;
public Lazy(T value)
{
this.value = value;
}
public Lazy(Func<T> loader)
{
this.loader = loader;
}
public T Value
{
get
{
if (loader != null)
{
value = loader();
loader = null;
}
return value;
}
}
public static implicit operator T(Lazy<T> lazy)
{
return lazy.Value;
}
public static implicit operator Lazy<T>(T value)
{
return new Lazy<T>(value);
}
}
Then you can use it like this in your entity :
class Entity
{
private readonly Guid id;
private readonly Lazy<SubEntity> subEntity;
public Entity(Guid id, Lazy<SubEntity> subEntity)
{
this.id = id;
this.subEntity = subEntity;
}
public Guid Id { get { return id; } }
public SubEntity SubEntity
{
get { return subEntity; } // implicit cast here
}
}
The code is more straight forward, the intent is clearly visible.
One benefit here, is that the subEntity field can be marked as readonly, that is a big improvement because our entity is really immutable now. Actually the Lazy<T> is mutable, but it behaves as an immutable value.
If your entity is not immutable, you can still leverage the Lazy<T> class :
class Entity
{
private readonly Guid id;
private Lazy<SubEntity> subEntity;
public Entity(Guid id, Lazy<SubEntity> subEntity)
{
this.id = id;
this.subEntity = subEntity;
}
public Guid Id { get { return id; } }
public SubEntity SubEntity
{
get { return subEntity; } // implicit cast here
set { subEntity = value; } // implicit cast here too
}
}
The last part is about how you use it when instantiating the object.
When creating a new instance in a factory (not linked to database) :
SubEntity subEntity = ...;
Guid id = ...;
Entity entity = new Entity(id, subEntity);
Here again, the implicit cast happen to pass the SubEntity as an already loaded Lazy<SubEntity>.
When binding the entity to the database :
class EntityBuilder
{
private IDataService dataService;
public EntityBuilder(IDataService dataService)
{
this.dataService = dataService;
}
public Entity GetEntity(Guid id)
{
return new Entity(
id,
new Lazy<SubEntity>(() => dataService.GetSubEntity(id)));
}
}
We can use a small helper method to make the instantiation cleaner :
public static class Lazy
{
public static Lazy<T> From<T>(Func<T> loader)
{
return new Lazy<T>(loader);
}
}
Then you can write :
return new Entity(
id,
Lazy.From(() => dataService.GetSubEntity(id)));
Now, the code that instantiate the Entity decides where the sub entity comes from.
The entity has become truly persistence ignorant.
Some would also advice not to use lazy load at all... this is still an option to consider !
Continued in Lazy loads and persistence ignorance (Part 2)
Comments
Definitely a very interesting article for a way to implement lazy loading short of going the route of dynamic proxies.
The only question I have is where would you use the EntityBuilder that wouldn't cause you to couple your domain objects to EntityBuilder the exact same way it would be coupled to IDataService you showed at the start of the post?
Your design gave me an idea for helping with problems around our repository's save method we've been encountering in our repository. The save() method had to go examine all the the domain objects, so to facilitate this we had to put ugly state management code into all our domain objects so they could report back what had been added/updated/deleted.
By passing Action<>'sinto the domain objects, I managed to keep state management responsibilities controlled by the repository and keep the domain objects insulated from the persistence interfaces. By having the domain objects delegate the state management back to methods passed by the repository, whether your repository uses a save method or updates the persistance layer immediately becomes an implementation detail within your repository, the domain objects behaves the same.
An example is:
class Component
{
private readonly Action<TEntry> _addAction;
public Component(Action<TEntry> addAction)
{
_addAction = addAction;
}
public void Add(TEntry entry)
{
_list.Add(entry);
_addAction(entry);
}
After briefly looking at your code, could one have lazy loading in a solution that uses NHibernate without resorting to proxy generation. I wonder how would NHibernate work with this, if it would still track changes, state etc.
@exp2000>A priori, the change tracking should work since it doesn't rely on proxy generation.
The main problem would be to hook into instance generation to pass delegates instead of values in ctor.
I have no deep knowledge of NHibernate yet, so I don't know how much work it represents.
Hi, I think you have quite literally taken the words "persistence ignorance" very very seriously.
The 1st example you have shown of lazy load is perefectly ok.
PI means we dont care how or where the entity is stored, we just call the save on the entity.. :-) doesnt mean we cannot even use the words repository at all.
As long as the layers dont get corrupted its fine ..
@googling> Sure ! And as you can read in other post, I realy don't like to inject services (here repositories) in entities. So...