O/R Mappers, the Law of Demeter, and Lazy Loading

 Sometime ago I blogged about the problems of using the same domain model from different layers.

 

Basically, my argument was that writing orderLine.Product.Price coupled your code with the object model structure, so I suggested it was better to write orderLine.ProductPrice.

 

This idea is not new, and is called the Law of Demeter.

 

One of the reasons I disliked O/R mappers was because, in my opinion, they forced you to write code such as orderLine.Product.Price. However, that's not necessarily true. A good O/R mapper should let you map the orderLine.ProductPrice to any field in the database that can be retrieved knowing the orderLine primary key.

 

For example, if the Price is not stored in the Product table but in a table that relates Customer and Product (to have a different price for each Customer), I can retrieve the product price from the OrderLine by using orderLine.Order.Customer.Id + orderLine.Product.Id to read CustomerProduct.Price.

 

Note that if I write orderLine.Product.Price and then the product price depends on the customer, there's no way to hide that. I cannot make Product.Price return the customer's price because I cannot obtain the 'Customer.Id' from the Product.

 

I don't know which O/R mappers support creating this mapping directly. But I do know Hibernate (a well-known Java O/R mapper) does not.

 

Of course, with any O/R mapper you can manually write the orderLine.ProductPrice to do something like the following:

 

public decimal ProductPrice

{

   get

   { 

      return CustomerProductFactory.Get(this.Order.Customer.Id, this.Product.Id).Price;

   } 

}

 

This way, you are not solving the problem at the mapping level but in the code.

 

Why do I think it's important to solve this at the mapping level? Because it's a good way to know when you need to eager load or to lazy load.

 

You are not going to add 'shortcut' properties in your classes for every reachable field. For example, if I'm not using the Product.Category.Name in the Order, then I won't have a CategoryName field in it.

 

If I add the shortcut, it’s because the field is relevant to the Order. So I can assume that if it's relevant to the order, it should be eager loaded.

 

In sum, if you want to apply the Law of Demeter to your domain model and your persistence layer, you should never write object.object.property, and you should use the shortcuts to know what to eager load.

 

If you know any O/R mapper that supports this at the mapping level, please let me know.

 

This is what we’ve always done with DeKlarit, but we are not an O/R mapper. In this case, we load DataSets with all the fields that are relevant to an Order, regardless of where they are stored.

5 Comments

  • LoD has the problem that it requires a lot of delegation. It's usually not justified, especially when applied generally for everything. When the client of a domain object knows that the object has a relation to another one, it's usually much simpler for maintainance to simply expose the related object on the interface (of course if you want to intercept the calls in the first object you shouldn't do that).

    When an Order has a Price depending on a Customer, it's a mere hack to expose the property and not to request the price explicitely passing the Customer as a parameter. The mapping is not the place for such dependencies which represent business/domain logic.

  • Hi Deyan,



    Thanks for the feedback!



    If my goal is to keep the interface of my domain model as stable as possible, then I think the LoD approach wins.



    If my goal is to have a domain model that matches closely the real object structure of the domain, then your approach is better.



    Note that if you try to see the Order from a 'real world' perspective (like seeing the Order on paper), then you'll know the Order has a Product Price, regardless on where it's really stored. This point of view makes me feel that the LoD approach has some truth in the sense that makes the model closer to what it looks in the real world.



  • Hi Andre,



    I agree with what you said. The point is that the LoD should be used carefully and not applied generally, otherwise you end up with much more unnatural and overburdened with delegation classes. I am usually trying to keep the stuff simple and as close to the data as possible.



    Best regards,

    Deyan

  • Additionally, I think that your initial example with the Order and the Price is incorrect. Do you calculate the Price every time someone invokes the getter? And what happens if between to get invocations the Price List/Customer price category changes? I think that you should calculate the price and after that assign it to the Order and save it in the Orders table. If there is a need for recalculation you can do it, but never dynamically upon Order.Price getter ....



  • Yes, you are right, but anyway somewhere in your code you are doing:



    Order.OrderProductPrice = Order.Product.Price



    Then the same reasoning applies to that 'Product.Price' call. Following the LoD you should write:



    Order.OrderProductPrice = Order.ProductPrice



    About doing a lot of delegation, it's certainly difficult if you are designing your domain model manually, but is not that bad if it can be handled by a tool.

Comments have been disabled for this content.