Grails does a good job of hiding the complexity of Java web-application programming, but it has to rely on some fairly elaborate mechanisms to do this. And, like any other lie, abstraction becomes more likely to unravel as it gets more complex. As The Bard tells us:
Truth will come to light; murder cannot be hid long; a man's son may, but in the end truth will out.A first encounter with the serialization requirement for objects in Grails webflow is like being a newlywed who has just unearthed a human femur while working in the garden. You have a choice. You can dig further to reveal the gruesome truth. Or you can shovel it over and try to set aside the possibility that your new beloved is an axe-murderer.
[1596 Shakespeare Merchant of Venice ii. ii. 73]
Our little tale of horror begins thus. On a bright spring day after the honeymoon, you decide to spend a pleasant morning planting new features in your shiny Grails app. The birds chirp merrily as you admire the neat rows of tillage you've done so far -- the clean domain classes, tidy controllers and orderly views bedewed with syntactic freshness.
You want to implement a forms-based work-flow -- and there in the tool-shed lies Grails webflow, ready to hand. Humming cheerily, you set to work and soon have several plain GSP views assembled into a flow something like this:
def buildFlow = { selectTemplate { on('next') { // capture template }.to('selectPersonnel') on('cancel').to('finish') } selectPersonnel { on('next') { // capture occasion personnel }.to('selectPersonnel') on('cancel').to('finish') } buildComplete { on('finished').to('finish') } finish { redirect(controller: 'occasion', action: 'list') } }This works flawlessly on the first try. So it's with a certain smug satisfaction that you mix in some GSP code you've used before in several views to populate a select control from one of your domain classes:
<g:select id="occasionTemplateId" name="occasionTemplateId" from="${OccasionTemplate.list()}" optionKey="id" noSelection="${['null':'Select One...']}" />You type in "grails run-app" and browse to your new webflow. Then, the sky darkens. The theme music grows eerie. The wind picks up, blowing dead leaves across your keyboard. In the command window are writ these lines of doom...
Could not serialize flow execution; make sure all objects stored in flow or flash scope are serializable Caused by: java.io.NotSerializableException: Venue"That's odd," you mutter, "Venue is a collaborator of OccasionTemplate, but I didn't use it anywhere. And what's this about serializable?"
You brush away some dirt with your copy of Grails In Action. According to Gina, Grails webflow requires any object stored in flow scope to implement Serializable.
You suspect that foul play is afoot. A class and all its collaborators must implement Serializable properly, something that's tricky to do and easily broken over time. But Grails webflow is asking you to do just that in all your domain classes. This is where the element of choice comes in. You can just kick a little dirt over that complexity. Add "implements Serializable" and hope for the best. But in the corner of your mind, you know. Grails webflow implements flow scope by serializing the object graph and restoring it with each request.
If you decide to keep digging, Grails In Action seems to offer a way back to the sunshine. Just use a command object and make it Serializable. But, like Jason, Complexity refuses to stay dead. Grails webflow insists that any domain object declared in a flow be Serializable, even if you haven't explicitly stored it in flow scope. So the Serializable command object only keeps the Bogie Man away as long as you steer clear of accessing domain classes in your flows. Good luck with that.
More digging online and you find the murder weapon. Grails webflow stores the Hibernate session (and therefore its cache) in the flow scope. That means that any domain object you've accessed in the flow gets stored in the cache and therefore must be serialized with the flow scope. The implications make you a little queasy. The victim was tortured -- performance-wise.
Following up, you find that the case is closed and the killer has gotten off scot-free. He's an untouchable.
That leaves you to seek justice alone. If you avoid the discard() method red-herring, you find a fairly simple solution, the Hibernate session clear() method.
def transient sessionFactory def buildFlow = { populate { action { def occasionTemplateItems = [] def occasionTemplates = OccasionTemplate.list() occasionTemplates.each { occasionTemplateItems << new OccasionTemplateItem( id: it.id, name: it.toString() ) } sessionFactory.currentSession.clear() // Why? [occasionTemplateItems: occasionTemplateItems] } on('success').to 'selectDate' on(Exception).to 'handleError' } }It's easy enough. But there it is like a skull grinning up from among the petunias. A statement completely out of place in its context. One that makes no sense unless you know the whole ghastly tale. Which now you do.
Good Night and... Pleasant Dreams...
Thanks for taking the time to post this but I'm not convinced this works. I'm having the same problem - don't want to have to start spraying all my domain classes with 'implements Serializable' and I need to instantiate them inside a flow but don't need to persist them (in fact, I'm going to extraordinary lengths to avoid it) but I always get the error the instant I try to get any record and way before I hand anything over to the view so I never get to make the call to clear the cache.
ReplyDeleteAny thoughts?
Well, it used to work. I used the sessionFactory.currentSession.clear() workaround successfully with Grails 1.2.2 and Grails WebFlow 1.2.2. However, I retested it using Grails 1.3.4 and Grails WebFlow 1.3.4 and the behavior is just as you have described. As soon as you instantiate a domain object or a command object in the flow that does not implement Serializable, you get a java.io.NotSerializableException.
ReplyDeleteI tried declaring the variables transient, using discard(), using Serializable wrappers and various other work-arounds, but to no avail. It seems that the loopholes are closed. The WebFlows developers say as much on the plug-in page in the section 'Known Issues and Quirks':
"Objects placed in flash/flow/conversation scope MUST implement java.io.Serializable"
They're not kidding. What's more, the placement now seems to happen as soon as the object is instantiated. Game over. Serialize or die.
This is pretty typical of frameworks in my experience. In exchange for the productivity, you have to accept that you're not always going to be able to do things your way.
That said, I also have to say that the reason I hadn't noticed that the work-around stopped working sometime after WebFlow 1.2.2 is that I had removed WebFlow from my projects entirely shortly thereafter. I found that I could implement all of my flow use cases using AJAX partial updates and stay on the same page. As it turns out, this improves both the user experience AND developer productivity (I was having to restart the server far too often when working with flows).
For what it's worth, I put the simple WebFlow example I used to test this on my public wiki:
ReplyDeletehttp://ridingthetiger.wikia.com/wiki/Creating_the_Example_Grails_WebFlow_Project
The challenge is to be able to remove the Serializable interface declaration from Contact. Go ahead, I dare you.
Enjoyable read. Nicely written Dave. Really liked this line "And, like any other lie, abstraction becomes more likely to unravel as it gets more complex.". It is so true when it comes to Grails.
ReplyDelete