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.
Everything that has an identity should be an Entity, and everything that does not have an identity is a simple value, hence a value object.
To quote Martin Fowler (which in turn qoutes Eric Evans)
- Entity: Objects that have a distinct identity that runs through time and different representations. You also hear these called "reference objects".
- Value Object: Objects that matter only has the combination of their attributes.
Reason to make your address a Value Object:
If your address is mutable, you will likely screw up your mailing history in the end. For example, if you're shipping items to an customer, you can't be sure to which address you actually shipped something in the past if the address your MailingHistory table is referring to has been changed.
The MailingHistory entry We shipped A764 to address 657 could mean We shipped article A764 to Boston yesterday and We shipped article A764 to New York tomorrow.
If the mailing address has to been changed, there's no need to delete the old one. Keep it, and mark it as inactive, and the new one as active.
Of course you could treat your address as a Entity, but only when updating it would not change the actual place the address is referring to, hence only allowing the correction of typos.
If you're sure you could ensure that, than using an Entity would be possible.
But the best solution IMHO is to not referrence an address Entity in your mailing history, but rather save the specific address directly in your mailing history table (basically copying the data of the address).
This way, you always know where you shipped your stuff (or whatever you are mailing), and since you would use a mutable Entity, your address table won't be cluttered up.
I've worked with/on several ERP systems, and nearly all of them used this approach.
You will have some redundancy in your database, but it's the most pragmatic way IMHO.
Best Answer
I really don't think there is problem of VOs having an identity in database, as long as this identity remains hidden and transparent in domain layer itself. I would be data layer's responsibility to keep track of those identities so the domain doesn't have to care about it.
But VOs still give you ability to denormalize. Denormalization can have it's advantages, usually for performance reasons. So you can use this to your advantage.