Stuff about programming, programming style, maintainability, testability. Dedicated to my coworkers and friends. Everyone is welcome to leave comments or disagree with me. This blog does not represent views or opinions of my employer.

Friday, August 15, 2014

I don't like Grails/Hibernate, part 4. Hibernate proxy objects.

Hibernate uses proxy classes to implement its lazy loading of nested domain objects. As a result, instead of BankAccount class (defined in post 1)  I may sometimes get object with a class named something like 'BankAccount_$$_javassist_27'.

The idea behind the general concept of a proxy is that: use of proxy objects should be identical to the use of real object. This utopian wish is impossible to accomplish in practice, and Hibernate proxies are no exception.  Hibernate proxies can cause several problems, including
  • gotchas related to equals method implementation (not discussed), 
  • the famous LazyInitializationException (which I have promised not to discuss) 
  • casting problems caused by use of polymorphic associations (this is a well documented issue and I will stay away from it as well).
So, the next best thing proxy designers (in any framework) can do is to make sure that the use of their proxies is not very transparent to the developer.  Good examples to think about here are proxies that show up in remoting technologies like: the old CORBA, RMI, EJB or even SOAP based WebService (brrrr).  In these technologies, developer had to do some specific thing, like a JNDI namespace lookup, to get access to a proxy.  By contrast, in Grails/GORM/Hibernate it is very hard to maintain a mental map of which object is a real object and which is a proxy.  This causes very intermittent behavior.

Probably for that reason, proxies had a rough start in Grails.  The term 'proxy hell' was coined and some interesting bugs like this one: have been reported.

Code Examples:
This code will get me a proxy object
  def id
  BankAccount.withNewSession {
     id = BankAccount.findByName('a1').id
  def a1 = BankAccount.load(id)
  assert simple instanceof HibernateProxy

Note, that as usual with Hibernate, I can break the above code by adding an isolated query:
  BankAccount.findByName('a1')  //added this line

  def id
  BankAccount.withNewSession {
     id = BankAccount.findByName('a1').id
  def a1 = BankAccount.load(id)
  assert simple instanceof HibernateProxy //now fails

You may find this last code example somewhat unrealistic, but I hope you agree with me that it explains the intermittent aspect of how proxies work (or why they often do not work).

How proxy code typically breaks:
Sooner or later, each Grails app will need to use domain object class to do various things with it like:
  • check if an object is a domain object or something else
  • introspect GORM properties on a domain object
  • find beans named after a domain object (for example, find BankAccountService if object is BankAccount)
And that is great, only, it is soooo easy to forget that, for example,    

may give me wrong class name! And if I do forget, my code will work, only, not always.

If you are very diligent in making sure that you always use the un-proxied classes only, then rest assured that someone else is not that diligent.  For example, here is a currently open Grails JIRA: and if you search source of, for example, various template libraries available out there you will find things like this code:

which, again, I am sure was tested and works ... only not always.

How to test for proxy problems:
Integration tests may find them but few developers code integration test separating these essential test elements into separate Hibernate sessions:
  • data setup (given)
  • test itself   (when)
  • test asserts (then)
(that lack of session isolation applies to Grails framework itself as shown by this open bug:  So Grails integration tests, as they are typically coded today, may not find most of proxy related issues. Functional tests are the best bet again.

Next topic: 
So far I have focused a class of Hibernate side-effects, where:
  BankAccount.findByName('a1') //(1)
  someOtherCode()              //(2)

adding isolated query (1) changes behavior of code (2).

I want to continue discussion about side-effects, but from a somewhat different angle. In post 1, I have promised not to talk about auto-saving, I will break that promise.  There are some very 'interesting' aspects of auto-saving worth examining,  for example, I sometimes get DB update operation errors from issuing a finder query.  I will try to examine a few of these interesting things.  Things that happen as a consequence of Hibernate design decision to make a major side-effect, like SQL update or insert operation, implicit and invisible.

No comments:

Post a Comment