Lazy loads and persistence ignorance (Part 2)
By Jérémie Chassaing on Sunday, February 8, 2009, 13:17 - Domain Driven Design - Permalink
In the previous post, I introduced a class to manage function injection for lazy loads to enable persistence ignorance.
Chris was asking where the builder I was talking about should be used, and I tumbled on a StackOverflow question asking how to make lazy loading without using classes like Lazy<T>...
I'll discuss the second part first.
There is a good way to make lazy loading without using classes like that. You can use proxies.
There are two main ways to make transparent proxies
- by creating dynamically a derived class using Reflection.Emit
- by creating a transparent proxy
Still there are some drawbacks with both approaches. For the first one, all your members should be made virtual, for the second your class should inherit from MarshalByRefObject. In all case you should then take care not to mess up between proxies and underlying classes.
The main critic to refuse to use Lazy<T> class is usually that it's not a domain concept, and that it leaks persistence concern in the model.
I reject those critics.
Have you ever seen someone criticize List<T> or Dictionary<,> because it was not a domain concept ? We're writing OO code, and we can use basic tooling to make our models expressive. IEnumerable<T> and Linq to objects are good examples of these useful tools.
And I don't consider that Lazy<T> is a persistence concern. It expresses that the relation between entities is a bit more loose than others. There is nothing in the Lazy<T> signature that ties your entity to any persistence concept. You just provide a way to give the value of the property when needed rather than at construction, but this choice comes from outside of your entity.
And at least it becomes clearer than with derived proxies where the C# keyword virtual is used to express something but tries to hide it in the same time.
For Chris question, I use the builder in the repository.
The repository is responsible for retrieving entities from the data store. The reconstruction of objects is a bit different from the construction. This is underlined in Evan's book in the chapter Factories / Reconstituting Stored Objects.
In my data access layer I use a IDataBuilder<T> interface that represents a service that can build an object of type T from a IDataRecord. This is when I work directly with the ADO.Net and stored procedures.
public interface IDataBuilder<T>
{
T Build(IDataRecord record);
}
I also use a class that encapsulate the access to the stored procedures (or queries if you don't want to use stored procedures).
public class DataAccess
{
public UniqueResult<Entity> GetEntity(Guid id);
{
//...
}
public MultipleResult<Entity> GetEntities()
{
//...
}
}
UniqueResult<T> and MultipleResult<T> provides a methods that take a builder to read data records :
public struct UniqueResult<T>
{
public T With(IDataBuilder<T> builder)
{
//...
}
}
public struct MultipleResult<T>
{
public IEnumerable<T> With(IDataBuilder<T> builder)
{
//...
}
}
In your repository implementation you can then use all this :
// The concrete repository interface that belongs to the domain
public interface IEntityRepository : IEnumerable<Entity>
{
Entity this[Guid id] { get;}
//.. other methods
}
// the implementation that is not part of the domain
// an that is pesistance dependant.
internal class EntityRepository : IEntityRepository
{
private DataAccess dataAccess;
private EntityBuilder builder;
public EntityRepository(DataAccess dataAccess, EntityBuilder builder)
{
this.dataAccess = dataAccess;
this.builder = builder;
}
public IEnumerator<Entity> GetEnumerator()
{
return dataAccess.GetEntities().With(builder);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Entity this[Guid id]
{
get { return dataAccess.GetEntity(id).With(builder); }
}
}
The last part is in the implementation of the builder.
internal class EntityBuilder : IDataBuilder<Entity>
{
private DataAccess dataAccess;
public EntityBuilder(DataAccess dataAccess)
{
this.dataAccess = dataAccess;
}
public Entity GetEntity(IDataRecord record)
{
var id = (Guid)record["Id"];
return new Entity(
id,
Lazy.From(() => dataAccess.GetSubEntity(id)));
}
}
Of course, the repository implementation, the DataAccess class and the builder are all internal to the data access implementation. It can be in a separate Assembly or internal in a sub namespace of the main assembly depending on you module separation preferences.
When using linq, you simply don't need the DataAccess class because you can query the DataContext directly. But you can use the same pattern.
The main point is still that your Entity object knows strictly nothing about what's going on there.
Comments
Honestly i don't see how you can claim that Lazy<T> is not a persistance concern. Can you think of a single case where you would use Lazy<T> if computers had only one type of storage?
Oh well, computers do have both fast volatile and slow persistant storage, so what you are describing looks very usefull and well thought thru :)
Great article!
@olav> and BTW, thanks for the compliments :-P
@Olav> Huhu you're true, it took quite some time to me to understand that Lazy<T> was not at infrastructure level like persistance, but at object model level like IEnumerable...
It's just a way to reference things that you need in some cases but can take time to retrieve.. and the association is not strong enough to justify to wait every time.
Be it file access, database access, internet access, long computation, etc.. it's a way to say : 'I don't own the data because it's just related data, but I should have access to it when needed.'
Im' still not sure it's good design, but it's better that the old
if (_childEntity == null) _childEntity = _repository.GetChildEntity(id);this version ties firmly the entity to the repository, and produce ugly mutable code. Yerk !
This Lazy<T> is an artificial construct and is a persistence concern. Comparing it to a List or Dictionary is like comparing cars to apples.
Your domain model should be free of any persistence concerns, it should be done in such a way that you won't need to inject anything into it to make it work.
And last - sooo much work for some obscure reason. Sorry, don't buy it.
I reject this idea (c)
@Alex> Lazy<T> itself has nothing to do with persistance,
it's rather like IEnumerable<T> for a single item.
It gives access to a single item without giving more information about where it comes from.
It respects observationale immutability. But it has no data access concern.
Then I'm ok that it's better to define clear aggregate roots and load whats needed in a row.
What pattern would you propose in cases you should delay load ?
Maybe I'm missing something, but to me this architecture seems to introduce a major problem:
the datareaders that your dataaccess classes return need to be closed or the cursors returned from the database remain open until the datareaders get garbage-collected.
I can't seem to find the place where this is happening in your code...
@Marc> The With(IDataBuilder<T> builder) method returns a generic enumerable and IEnumerator<T> has the IDisposable interface.
The foreach keyword calls Dispose on the enumerator on completion, and all linq chaining operators do the same.
MultipleResults contains a reference to the underlying data source (think SqlServer connexion string) and catches the call context (think sql script or stored procedure name and parameters values). The code for With will be something like :
IEnumerable<T> With<T>(IDataBuilder<T> builder) { // the Enumerable returned by Select will call Dispose //on the enumerable called by GetRecords when disposed. return GetRecords().Select(builder); }private IEnumerable<IDataRecord> GetRecords() { connection.Open(); try { using (var reader = command.ExecuteDataReader()) { while (reader.Read()) yield return (IDataRecord)reader; } } finally { connection.Close(); } }When the iterator (containing yield return) is compiled, the using and finally parts are implemented in the resulting IEnumerator.Dispose method. It will be called when enumeration is finished on the other side.
This is exactly what happens also in Linq to SQL, and this is the reason why IEnumerator<T> has been implemented with IDisposable in .Net 2.