Friday, March 12, 2010

Live Service Reloading in Tapestry 5.2

A common question I get during Tapestry training sessions is: Why can't Tapestry reload my services as well as my pages and components?. It does seem odd that I talk about how agile Tapestry is, with the live class reloading, and how nicely OO it is, what with services ... but when you move common logic to a service, you lose the agility because services do not live reload.

This came up yet again, during my latest training session, in London.

I've considered this before, and I've been opposed to live service reloading for a couple of reasons. First, live reloading requires creating new class loaders, and that causes conflicts with other frameworks and libraries. You get those crazy ClassCastExceptions even though the class name is correct (same name, different class loader, different class). Further, in Tapestry IoC, services can be utilized to make contributions to other services ... changing one service implementation, or one module, can cause a ripple effect across an untraceable number of other services. How do you know what needs to be reloaded or re-initialized?

When I last really considered this, back in the HiveMind days, my conclusion was that it was not possible to create a perfect reloading process: one that would ensure that the live-reloaded Registry (and all of its services with all their internal state) would be an exact match for what configuration you'd get by doing a cold restart.

So I shelved the idea, thinking that simply redeploying the WAR containing the application (and the services and modules) would accomplish the desired effect.

But as they say, The Perfect Is The Enemy Of The Good. One very sharp student, Andreas Pardeike, asked: Why not just reload the service implementations?.

Why not indeed? Why not limit the behavior to something understandable, trackable, and not very expensive. Most of the infrastructure was already present, used for reloading of component classes. What about ClassCastExceptions? In Tapestry, service implementations are already buried under multiple layers of dynamically generated proxies that implement the service interface. The underlying service implementation is never automatically exposed.

A few hours of work later ... and we have live service reloading. Each reloadable service gets its own class loader, just to load the service implementation class. When Tapestry is periodically checking for updated files, it checks each reloadable service. If necessary, the existing instance, class and class loader is discarded and a new class loader created for the updated .class file.

This is going to make a big difference for me, and for most Tapestry developers. Both applications I'm working on have enough Hibernate entities and other clutter to take some time (20 - 30 seconds) to restart, and most functionality is hidden past a login page. Being able to change a service, for example to tweak a Hibernate query, with the same speed with which I can tweak a template or component class, is just one more thing to keep me in the flow and super productive.

Give it a try ... it's one more step towards making Tapestry so compelling, you wouldn't think of using anything else!

10 comments:

  1. You could do this with OSGi -- but then you'd have two problems :-)

    ReplyDelete
  2. This sounds fantastic Howard, does only the changed implementation class/es get reloaded? or all of them?

    The later sounds as if it could be less scalable? my concern is that we have a few thousand service implementations, so reloading all of these could do more harm than good for us if this was the case.

    ReplyDelete
  3. Tom,

    the idea is to reload the service implementations at development time. You change your class, save it, switch to the browser, reload the page and see the results immediately. No server shutdown is needed.

    We don't want to update any services at runtime, so OSGi is not an option for Tapestry.

    This feature is more comparable with JRebel. With Tapestry you have a light version of JRebel built it. This reduces the turnaround costs and gives Tapestry a further advantage over competing web frameworks.

    ReplyDelete
  4. @Igor,

    Check the code :-)

    Each individual service gets its own class loader, so it's not like components. This works because the implementation will just see the proxies for other services.

    ReplyDelete
  5. congratulations, this will speed up even more development time :)

    ReplyDelete
  6. Anonymous12:38 PM

    That could speed up simple developments, but for example, on my current project, the services are inside another project, which is imported as a dependency with Maven.

    ReplyDelete
  7. @Bouil Not much Tapestry can do there, I'm not sure how different servlet containers will react to a JAR file changing out from underneath them. In any case, you can always develop services in an agile manner as part of an application, then (once the behavior is nailed down), refractor out to a library module.

    ReplyDelete
  8. Thanks for another good change in T5. Can somebody pl put the link as I could not find the link which will download this latest change?

    ReplyDelete
  9. It's available as a snapshot artifact from the Tapestry Maven snapshot repository. http://tapestry.formos.com/maven-snapshot-repository/

    ReplyDelete
  10. Woot! This would make writing code more fun, more productive.

    P.S. Where's the Tapestry5 book?

    ReplyDelete

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费