Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

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:

Tom said...

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

Peter Stavrinides said...

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.

Igor said...

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.

Howard said...

@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.

Alfonso said...

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

Bouil said...

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.

Howard said...

@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.

Pawan Kumar said...

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?

Howard said...

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

killer_tilapia said...

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

P.S. Where's the Tapestry5 book?