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!

Wednesday, September 15, 2004

Whew! Full speed ahead!

The vast refactoring of Tapestry continues. Today I completed my first pass. All the major subsystems of Tapestry (such as ISpecificationSource, ITemplateSource, DataSqueezer, IScriptSource, etc.) are now HiveMind services. Yes ... everything still runs pretty much identically, but under the covers the code is evolving into smaller, simpler classes.

In addition, the first real changes (still, not visible though) are now in place. There is now a collection of BindingFactory implementations, one for each type of binding (OGNL expression, message keys, and literals). There's a BindingSource service that knows how to take a string (from an HTML attribute value) and find the correct BindingFactory, based on prefix, to generate a IBinding instance from it.

This is now being used for all bindings created by the SpecificationSource and the ComponentTemplateLoader. Its driven by a configuration, which means that it will be super easy to add additional binding prefixes (even application specific ones). This is the first, approachable, step along the path to what will be Tapestry 3.1.

It's a great, iterative process. New code I create is truly testable ... I hope to start retiring many of the integration tests (the ones driven by XML script files) since those are pretty darn slow to execute compared to focused unit tests.

I had one great headache; I converted a utility class (BaseComponentTemplateLoader) into a service (ComponentTemplateLoader). BaseComponent used to create one instance of this every time a component loaded its template. I converted this to a threaded service, because it had some internal state.

After many headaches, I realized that BaseComponent was doing it right ... the state wasn't merely threaded, it was reentrant. Loading a component will often cause a new component to be created ... and if that new component has a template, the process goes recursive. I didn't realize that, and started getting bizarre, impossible results. Eventually, I realized what was going on ... and my final solution is very similar to where I started. ComponentTemplateLoaderImpl is a normal service (not threaded), but delegates just about all of its behavior to ComponentTemplateLoaderLogic, which is created (as before) for each component and discarded immediately.

This makes me think that HiveMind needs a reentrant service model. I think Spring has something like this, "prototype beans", where each method invocation causes a new implementation instance to be created. That would be easy enough to do in HiveMind.

It has been very slick seeing the mix of service implementations; most of the Tapestry services are ordinary singletons. A few, which hold client-specific state for the duration of a request, are threaded. However, when these are combined together, the singletons and threaded code doesn't have to know anything when they invoke each other ... its just objects and interfaces and the mechanics of locating a thread-specific instance to process a particular service method is entirely hidden.

So, Tapestry is getting more powerful and simpler at the same time. That's hard to beat. Not sure when I'll have time for more or what I'll hit next ... I'm following a kind of round-robin approach, where even the code I've created recently is ready to be refactored into smaller and smaller pieces. Probably next up is to start thinking about engine services. There's also refactoring around OGNL 3.0. Or getting ready for the major rework for modularity (splitting the application across multiple folders). It's all good!

No comments: