Persistence-Ignorant Objects – Can They Implement Lazy Loading?

design-patternsdomain-driven-designdomain-modelpersistence

Persistence Ignorance is an application of single responsibility principle, which in practice means that Domain Objects (DO) shouldn't contain code related to persistence, instead they should only contain domain logic.

a) I assume this means that the code which contacts lower layers ( ie persistence layers ) lives outside of the domain model in other classes ( OC ) of a business logic layer?

b) If my assumption under a) is correct, then DO, say Customer, never contains methods such as GetCustomers or GetCustomerByID?

c) If my assumptions under a) and b) are correct, and assuming Customer domain object uses lazy loading for some of its properties, then at some point Customer's internal logic must contact OC, which in turn retrieves deffered data. But if Customer needs to contact OC to receive deffered data, then we can't really claim that Domain Objects don't contain logic related to persistence?!

Thank you

REPLYING TO jkohlhepp

1) I assume OrderProvider and CustomerProvider classes are contained within business logic layer?

2) I gather from your reply that my assumptions under b) are correct?

3)

… I would check to see if some private orders field was populated or
if it was null. If it is null …

But as far as I can tell, as soon as domain code needs to check whether private order field was populated, and if it isn't,contacting OrderProvider, we are already violating PI principle?!

Best Answer

I believe you are correct in your assumptions A and B around persistence ignorance.

How you would best accomplish lazy loading of database objects is greatly dependent on your particular problem and implementation. However, I will attempt a generic answer to how to do lazy loading while still maintaining separation of concerns between persistence and domain logic classes.

I tend to implement persistence ignorance using the following classes:

  • Domain classes - e.g. Customer
  • Provider / repository classes - e.g. CustomerProvider
  • Generic database querying classes - e.g. DatabaseQuery

The DatabaseQuery class would be responsible for using the database driver to query the database and assemble the resulting data into a generic result set such as a DataTable. The CustomerProvider would be responsible for using the DatabaseQuery class to execute SQL against the database and use the results of that SQL to assemble Customer instances. Customer would be "pure" domain object that contained data and logic related to customers.

As for whether the provider classes should be in the business tier or the data tier, I don't have a strong opinion. I can see a case for both. The important part is that you separate the responsibilities across classes.

So now let's discuss lazy loading. Let's say I wanted Customer to have a collection of Orders, but I don't want to pull Orders out of the database unless the consumer tries to access them. I would create a property on Customer called Orders. In the getter of that property, I would check to see if some private orders field was populated or if it was null. If it is null, load the orders from the database using an OrderProvider. Otherwise, just return the collection that was already loaded.

In my opinion, the need for Customer to contact OrderProvider does not violate PI. Customer still doesn't know how it gets orders. It just knows that it gets them from OrderProvider. There might be other reasons to decouple Customer from OrderProvider, but I don't think PI is a problem here.

This assumes that you are doing persistence ignorance manually. If you are a using an ORM framework such as Entity Framework or Hibernate, then those frameworks generally have features for supporting lazy loading automagically.