I’m building an application that will receive inventory against a PO. This is far more complex than the typical CRUD model seen in many Grails examples. Naturally, I chose to put all the business logic for this transaction into a service. In a nutshell, my service takes the PO number, item number, and quantity, and then updates the receipt quantity on the PO, creates an inventory record, creates a history record of the inventory creation, generates a receipt transaction for the host system, and so on… This is all something I want to occur in a transaction. If any step fails, I want to roll it all back. A half baked transaction here, would be a bad thing.
A Grails service seemed perfect for this. By default services are created as singletons, and the Spring framework is used to manage transactions. A runtime exception anywhere in the method will rollback the transaction. I like this concept because it leaves the service loosely coupled. I can call it from anything. A web form via a controller, or maybe have another service for processing messages off a JMS queue.
So I built my service as describe above. Like a good Grails developer it was complete with unit tests and integration tests. Everything was working well with the my service to receive inventory against a PO.
I hadn’t worked with WebFlows before, but they seemed like the ideal candidate for my web application. My application is for a mobile device for a person on the warehouse floor to receive inventory against a PO. They will need to enter their location, then the PO, then the item to receive. From here I want to show them a description of the item, and allow entry of the quantity, status, and license plate (A unique id for a pallet or carton). After receiving an item, go back and enter another item number for another receipt on the current PO.
It was a piece of cake to wire up my application flow using Grails webflows. Under the covers Grails is using Spring’s webflows. The Grails DSL makes things very easy.
And then my headaches started. Every time my webflow touched a domain object, I’d get serialization errors. This really threw me for a loop, because I was not trying to store any of the domain objects into the flow scope. It would have made sense to me if I was trying to store the domain object into the flow scope. Ugh. I really did not want to go back and make ALL of my domain objects serializable to work around this ‘feature’. Plus, if you are using custom validations in your domains (as I found by accident), you cannot serialize the domain object. Groovy objects with closures (aka custom validator), cannot be serialized.
To make a long story short, I stumbled across a post by Ian Roberts explaining the Hibernate cache for your session is serialized in the Spring webflow. As a solution, Ian recommended to call the .discard() method on any domain object you worked with. The problem I ran into, is when you invoked a service, the service could leave objects in the Hibernate cache. Now being the lazy developer that I am, I really did not want to modify all my services to make sure every domain object was either serializable or had the discard method called. Plus that solution is just plan ugly. I just saw a future of headaches, because someone forgot to call the discard method.
Actually, there is an easy solution for this. In your service you just need to get a hold of your hibernate session, and call the clear method on it. Do this as the last step in your webflow, and all is good.
Now, onto the next problem. Web Flows will also want to serialize your service. According to the Grails documentation, you need to set the scope of your service to ‘flow’, and manually manage your own transactions. Grumble. This is not what I want. I do not want a tight coupling between my Web Flow controller and my service. My service should be, well a Service!
Transient is your friend. My workaround here was to mark the service property in the Web Flow controller as transient. Now, this property will not be serialized. So far, this seems to be working well for me. Although, I still want to test rollbacks further.
Here is a quick example of my work arounds:
class FooController {
def transient myService
def transient sessionFactory
def someFlow {
on "someEvent" {
myService.someMethod()
. . .
sessionFactory.currentSession.clear()
}.to "nextState"
You can see myService and sessionFactory are marked as transient. At run time these will be injected for you by Spring (as is normally done in Grails). Via the sessionFactory object, we can get our current Hibernate session and clear the cache. Now, all will be happy.