First, an alternative
The ShippingAddress is very expensive to load and located in a separate table, and a minority of pages use it.
In that case, perhaps ShippingAddress
doesn't belong in Person
in terms of the Business Object model?
For the cases where you need to involve ShippingAddress
as a first-class concept then you can instead define a ShippingAddress
business object, which can of course have a Person
as a dependency if needs be, or better yet just represent Shipping Addresses as a service, load them when needed (based on PersonID or whatever) and directly attach them to the Person
instance.
Second, an exceptional case
If that doesn't / can't float your goat, then let's stick with Lazy Loading.
I slightly disagree with your statement that "from a business logic/consumer/signature perspective there is no reason ShippingAddress
should be any different the eagerly loaded Name property".
I totally see the perspective; and in general I would agree - except that in this case you have a specific, real-world situation (horribly slow access to the Shipping Address data store, cannot be changed, these things happen) that unfortunately defies the normal expectancy that if you've seen one getter
, you've seen 'em all :)
So I would say that a client accessing the Address
** property would be surprised to find that it actually involves a separate database hit, especially considering
- The other properties don't
- This is a property on a Business Object, not an obviously "connected" entity.
TL;DR - don't violate POLA - consider what might happen if someone throws a bunch of Person
objects into an XmlSerialiser
....
Third, a suggested approach
If you are going to use lazy loading from the database for Address, I'd recommend one of two approaches, both of which involve making the actual loader itself an explicitly modelled abstraction.
1. Make the loader function an explicit dependency of Person
public Person(int name, etc, IShippingAddressLoader addressLoader)
2. Make the loader function an explicit dependency of a LoadAddress() method
public void LoadAddress(IShippingAddressLoader addressLoader)
{
_address=addressLoader.Load(_personID); //or whatever;
}
My reasoning is
In both ways, I think it's more obvious to client code using the Person class that there is "extra" code involved in getting the Shipping Address; and as a bonus it makes the Person
class much more unit-testable because you can mock or stub the loader.
With either approach you don't need to declare _address
itself as Lazy because you have shifted the "laziness" onto an explicit concept (the loader).
When you do find a nice way to cache all of the Addresses (maybe your server gets more RAM, so now it's feasible to load them all into memory at app startup, slap a SqlDependency on them and watch for changes), you only need to change the IShippingAddressLoader implementation, not the Person
class.
Finally, a sneaky attempt
to sell you the first idea by suggesting that you just accept that Shipping Address is a tricky piece of data in your case, and that any code that had to deal with it (controllers or whatnot) just get their own IShippingAddressService
service, e.g
public ShippingConfirmationModel GenerateConfirmationModel(Person person)
{
var shippingAddress = _shippingAddressService.GetByPersonID(person.ID);
person.SetShippingAddress(shippingAddress)
return new ShippingConfirmationModel(person);
}
This frees you from all shenanigans related to lazy loading in the Person class, allows you to implement caching really easily, and also allows you to pass Person objects around safe in the knowledge that you won't accidentally tip over the database when someone decides to serialise all the people :)
**Minor note - if this is the Person's "ShippingAddress" then I would make sure to refer to it always as "ShippingAddress" in the code, as opposed to just "Address" :)