views:

237

answers:

1

A fellow developer and I are conversing (to put it lightly) over Lazy-Loading of Properties of an object.

  • He says to use a static IoC lookup call for resolution and Lazy-Loading of objects of an object.
  • I say that violates SRP, and to use the owning Service to resolve that object.

So, how would you handle Lazy-Loading following IoC and SRP?

You cannot Unit test that lazy-loaded property. He rebuttles that one saying, "you already have unit tests for the UserStatsService - there's your code coverage." A valid point, but the property remains untested though for "complete" coverage.

Setup / code patterns:

  • Project is using strict Dependency Injection rules (injected in the ctors of all services, repositories, etc).
  • Project is using IoC by way of Castle (but could be anything, like Unity).

An example is below.

public class User
{
  public Guid UserId { get; set; }

  private UserStats _userStats;
  // lazy-loading of an object on an object
  public UserStats UserStats
  {
    get
    {
      if (_userStats == null)
      {
        // ComponentsLookup is just a wrapper around IoC 
        // Castle/Unity/etc.
        _userStats = 
          ComponentsLookup
            .Fetch<UserStatsService>()
              .GetByUserId(this.UserId);
      }
      return _userStats;
    }
  }
}

The above shows an example of lazy-loading an object. I say not to use this, and to access UserStatsService from the UI layer wherever you need that object.

EDIT: One answer below reminded me of the NHibernate trick to lazy-loading, which is to virtualize your property, allowing NHibernate to create an over-load of the lazy-loading itself. Slick, yes, but we are not using NHibernate.

No one really tackles the matter of Lazy-Loading. Some good articles and SO questions get close:

I do see a benefit of lazy-loading. Don't get my wrong, all I did was lazy-loading of my complex types and their sub-types until I switched to the D.I.-ways of the ninja. The benefit is in the UI layer, where a user's stats is displayed, say, in a list with 100 rows. But with DI, now you have to reference a few lines of code to get that user stats (to not violate SRP and not violate the law-of-Demeter), and it has to walk this long path of lookups 100+ times.

Yes yes, adding caching and ensuring the UserStatsService is coded to be used as a Singleton pattern greatly lower the performance cost.

But I am wondering if anyone else out there has a [stubborn] developer that just won't bend to the IoC and D.I. rules completely, and has valid performance/coding points to justify the work-arounds.

+2  A: 

Entities themselves should not have the responsibility of lazy loading. That is an infrastructural concern whose solution will lie elsewhere.

Let's say an entity is used in two separate contexts. In the first, its children are used heavily and are eagerly-loaded. In the second, they are used rarely and are lazy-loaded. Is that also the entity's concern? What would the configuration look like?

NHibernate answers these questions by proxying the entity type. A property of type IList<Entity> is set by the infrastructure to an implementation which knows about lazy loading. The entity remains blissfully unaware. Parent references (like in your question) are also handled, requiring only a simple property.

Now that the concern is outside the entity, the infrastructure (ORM) is responsible for determining context and configuration (like eager/lazy loading).

Bryan Watts
+1 I completely agree that the concern should be outside of the entity. With NHibernate (which I know how it overloads the lazy-loading property, yep), this would be easy. But, we are not using NHibernate. Also, in the rare-use case you describe (without NHibernate), this would be a prone to using a lot of memory - having the child properties loaded at repo level. Hence, the lazy-loading concept for only-when-needed cases, of high performance. I will continue to look into other patterns.
eduncan911
Implementing lazy-loading underneath `IList<>` is independent of NHibernate; that is just an easy example. Its main benefit over static calls is context sensitivity. When you put a static call in a class, you automatically make its context the entire AppDomain, requiring that every instance work exactly the same way. Let's say you have a list page and exports for an entity. You might lazy load for list pages, but eager load for exports (since you know you will process everything). This kind of contextual behavior (and others such as batch sizes) is essentially ruled out by static calls.
Bryan Watts
Marking this as an answer for now (got to keep my 100% accept rate!). Heh.
eduncan911