Geeks With Blogs
// ThomasWeller C#/.NET software development, software integrity, life as a freelancer, and all the rest

EDIT:
It turned out that the original example herein is not very clear about the point I want to make. Therefore I posted a follow-up with a better and more precise example (see here).

Recently, I came about this blog post, which describes an interesting problem about NHibernate, lazy loading, and polymorphism. My initial thoughts were that this would be another one of these fancy technical details that a developer has to deal with all the time. But after a while it grew bigger and bigger in my mind and now I consider it to be a 'real' problem that has the potential to question our entire understanding of architectural layering, if you're not aware of it.

This is the first one of a two-part post. In this part I will describe the problem in some detail and I will also explain why I think it's a big one. The second part (which I will post within the next few days) will then present an architectural approach to avoid the here outlined problems.

The scenario

As an example, I will use a classic many-to-one relationship like it regularly occurs in every project. (It's important to understand that the following is far from being somehow exotic or exceptional.) Here's the - intentionally simple - 'domain model':

CD_WithClasses

And this is the corresponding 'data model':

DataModel

Nothing unusual here also - the data model is even simpler than the class model, because the entire Author inheritance tree is mapped to a single table. A Book has an Author, which is accomplished by the 'BOOKS.AUTHOR->AUTHORS.ID' foreign key relationship. The actual type of the author is determined by the AUTHORS.TYPE column, which is one of 'P' or 'E'. (Note that something like an AuthorType property is not present in the domain in any way, the concept of "An author must be either a person or a group of editors" is expressed by the author's concrete class type. The TYPE database column serves only as discriminator value for persistence purposes.)

With NHibernate, the mapping between classes and database tables would be:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="LLDemo.Core" namespace="LLDemo.Core">

  <class name="Author" table="AUTHORS" abstract="true">

    <id name="Id">

      <generator class="native" />

    </id>

    <discriminator column="TYPE"/>

    <property name="Line" />

    <subclass name="Person" discriminator-value="P"/>

    <subclass name="Editors" discriminator-value="E"/>

  </class>

</hibernate-mapping>


...


<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="LLDemo.Core" namespace="LLDemo.Core">

  <class name="Book" table="BOOKS">

    <id name="Id">

      <generator class="native" />

    </id>

    <property name="Name" />

    <many-to-one name="Author"/>

  </class>

</hibernate-mapping>

Quite trivial stuff so far, and definitely not worth a blog post. But wait, there's more...

Equipped with the above depicted knowledge about the 'domain model', it seems absolutely reasonable and natural to write code like this:

Book book = GetABook();

 

if (book.Author is Person)

{

    // do something person-specific

}

else if (book.Author is Editors)

{

    // do something editors-specific

}

And this is where the trouble starts. Because NHibernate uses lazy loading by default, the Author property will never be of any type defined in our code, but it will always be a dynamic proxy created by the ORM at runtime - this remains true even if the Author finally gets loaded: The proxy won't be replaced by the 'real' entity (this is simply not possible in .NET), instead the calls will be forwarded to the now loaded Person entity. So the above code will never work - which is btw. not something NH-specific, but will appear in every ORM by design, if it supports lazy loading and inheritance.

To see the problem in some more detail, look at this test:

[Test]

public void TheAuthorOfFightClubShouldBeAPerson()

{

    using (ISession session = this.sessionFactory.OpenSession())

    {

        Book fightClub = session.Get<Book>(1);

 

        Console.WriteLine("This is just to see lazy loading happening...");

 

        Assert.IsNotNull(fightClub.Author);

        Assert.AreEqual("Chuck Palahniuk", fightClub.Author.Line);

 

        // this fails when lazy loading is used...

        Assert.IsInstanceOfType(typeof(Person), fightClub.Author);

    }

}

This gives the following result - the console output together with the failure message illustrates quite clear what's going on:

Think about that for a minute. What does it mean in the end?

It means that the entire concept of Persistence ignorance is at risk here! The whole point of Persistence ignorance is that the domain should be unaware of how, or even if, it is being persisted. Or, in other words: "There is no database". But having the here described scenario, it is far too easy to introduce errors like the above. To avoid such things, you have to know a) that an ORM is used to handle persistence, b) that the ORM is configured to use lazy loading, and c) you need some knowledge about how an ORM works. This is one of the cases where seemingly simple classes can only be handled correctly if the developer has the complexity of the underlying persistence system in mind, although there is no obvious dependency between the Author class and the way it is persisted. Everything seems to be pure POCO/Persistence ignorance...

While the above outlined error source might not be a big deal for the initial developer, it can become a huge, annoying problem for the poor guy who has to deal with this code in the future. Imagine yourself starting a new job and inheriting a large codebase that seems to be fairly well-structured and good to maintain. Let the codebase be 10-15 years of age (which is far from uncommon in real life). You have no detailed knowledge about ORMs - they might be out of fashion at the time and anyway the domain doesn't depend on the persistence layer in any respect, right? Now you have to do an extension, and in the course of this you introduce code like the above, without any suspect. I promise you: You will have a nice time finding and understanding the problem (and your company will have to spend a lot of money for it)...

I will end this here (okay, somewhat abruptly, but it's already too long, I think...), leaving the 'solution' for the second part of this post...

Posted on Sunday, September 6, 2009 8:07 PM NHibernate , Architecture and Design | Back to top


Comments on this post: Lazy loading, Inheritance, and Persistence ignorance (part 1)

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
introduce a virtual DoSomething() method in the Authors class and make both Person and Editor implement it with their specific behavior

this eliminates the need for down-casting (which is almost always a bad idea), and is actually proper OO. After all, you have a reference to the Author class, and you need the author to do something. Whether the Author instance is actually a Person or an Editor shouldn't even matter to your calling code.
Left by Davy Brion on Sep 08, 2009 6:06 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
Davy,

of course you're right. It's trivially simple to circumvent all these problems if you know what's going on.

My point is that this contradicts the concept of 'Persistence Ignorance' in a way: You have to avoid something that otherwise would be perfectly legal .NET code, and you can do this only if you know what's happening behind the scenes...
Left by Thomas Weller on Sep 08, 2009 6:24 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
that code might be perfectly legal .NET code, but it also contradicts the concepts of object-orientation ;)
Left by Davy Brion on Sep 08, 2009 6:50 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
While actual down-casting might indeed be problematic in many cases (not all, I think), an expression of type 'if (x is y)' is nothing but a simple boolean comparison. How could that be non-OO?
Left by Thomas Weller on Sep 08, 2009 7:48 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
The "non-OO" part is that you're asking, not telling.

Code consuming Author objects should be able to treat them all identically, regardless of whether they're actually instances of Person or Editors (or even proxies to them). This is what polymorphism, the Liskov Substitution Principle, and "Tell, Don't Ask" are all about.
Left by Jason Diamond on Sep 09, 2009 6:45 AM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
Jason,

what you are saying is only true for typecasts and only within a certain level of abstraction.
But I'm _not_ talking about typecasts here, but about checking an object's type - which is not the same.
Think for example about implementations of Object.Equals()/IEquatable<T>.Equals(): They will usually check the type of the object to compare with, but not to cast it in any way.
Left by Thomas Weller on Sep 09, 2009 7:16 AM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
Hi Thomas,

I agree that implementing Equals the "traditional" way would be a problem in this scenario.

Both Davy and I were commenting on the actual example in your post that was doing something person-specific or editors-specific after checking its type. That's the OO no-no.

As far as implementing Equals goes, I haven't seen it be a problem yet for me.

For entities, they're equal if their IDs are equal. NHibernate guarantees that two entities with the same ID will have the same object identity in the context of a single session. This means you don't usually need to override Equals.

For value objects (in the DDD sense--NHibernate refers to these as components), their identity isn't important (they don't even have a primary key) so your Equals method would do a property-by-property comparison. I can't think of needing to do this or even having used inheritance with value objects to where this would be an issue.

I think your original point was that it's not really possible to be completely persistence ignorant and I agree. After all, most of us using NHibernate are sticking Id properties on all our entities. I think it's good enough, though. As long as I can test my domain objects without a dependency on the DB, I'm happy.
Left by Jason Diamond on Sep 09, 2009 9:52 AM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 1)
Requesting Gravatar...
Jason, Davy,

the example from the original post is admittedly somewhat unclear.

I posted a follow-up ('v1.5') with another and hopefully better example to describe the problem (it's not about casting and polymorphism...).

- Thomas
Left by Thomas Weller on Sep 10, 2009 8:06 AM

Your comment:
 (will show your gravatar)


Copyright © Thomas Weller | Powered by: GeeksWithBlogs.net