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, July 30, 2004

Ant best practice: One Compile, One Directory, One JAR

Do yourself a favor. When you are laying out your source tree, follow this rule: One Compile, One Directory, One JAR. Just because Ant supports includes and excludes on its <javac> and <jar> tasks doesn't mean you should actually use them!

A proper source tree structure may have multiple source code roots, and will run javac multiple times -- but each execution will compile all files under a particular source directory into its own, individual classes directory. The contents of that directory (perhaps combined with some deployment descriptors or other resources) will form a JAR. If anything depends on that code, it should add the JAR (not the classes directory, and certainly not the source directory) to its compile time classpath.

Why is the compile-and-split approach dangerous? It makes the build process and build scripts more complicated -- possibly lethally so. It ensures that the build process will not generate class files the same way (and into the same directories) as the IDE. It increases the likelyhood that some classes will be duplicated (compiled multiple times to multiple directories and packaged into multiple JARs), which can easily lead to mysterious ClassCastExceptions at runtime.

A particularly important case involves deployment into an application server. Once you get into complex EAR deployments (with multiple EJB JARs and WARs), the class loader issues can quickly get out of control. By using multiple compiles, you can cleanly ensure that the libraries available at build time match the libraries available to the runtime classloader for that artifact (that EJB JAR or WAR). Without this simple bit of discipline and organization, you can lose big chunks of time to tracking down ClassNotFoundExceptions.

I'm always amazed when I see this kind of thing in otherwise sane code and build environments. I've also seen just how far wrong this approach can go ... such as one source tree, one compile, 40+ JAR files, 10,000+ lines of build scripts!!

.Net vs. Struts ... and the winner is Tapestry!

Just something I noticed on the Cardsharp on Software blog:

Tapestry from the Jakarta Project. Yes, it's not JSF. Yes it's "non-standard." Yes, it requires you to learn a new library and a new way of doing things. But it's better. Far less configuration than Struts, and what is there makes sense. Instead of a jumble of disconnected concepts, the whole library is built around an event-driven mental model that just makes sense. Things that would take me 5 lines of code in Struts take one or two in Tapestry. Imagine being able to tie an event handler in a Java class to a link or a button with no configuration file changes! Think about forwarding to a new page and passing objects to the "action" by setting properties on it istead of hucking everthing into the request or session. Think about how much you like Tiles and imagine what it would be like if everything was a tile, but didn't require all the verbose <tiles:put> lines just to set parameters on your tiles. Think about all those times you'd wished you could put custom configuration information into your <ACTION> tags in the struts-config.xml (especially if you've tried to customize it using the <set-property> nonsense!). Think about how nice your life would be if your ActionForm and your Action were actually in the same class in a way that made sense, and think about how nice it'd be if the framework implemented all the getters and setters for you. Think about implementing complex interactions without jumping through all kinds of hoops to manage the state of your page.

And I'm only scratching the surface here.

Thursday, July 29, 2004

This Great Hacker Does Use Java

Contrary to Paul Graham's misguided treatise on Great Hackers, this great hacker (by his definition, I prefer the term "developer") absolutely does use Java and loves it. You can pry it out of my cold, dead hands kind of love.

Side note: coming from an Objective-C background, I was initially very dismissive myself of Java. I still prefer many aspects of the Objective-C syntax, and Objective-C has killer runtime libraries ... but I've grown to love and be reliant upon Java type safety, and garbage collection. Both are pre-requisites for solving problems in an object oriented way: using large numbers of small, simple objects.

I feel there is a kind of insipid game out there I call "chase the gigahertz". Every time the language (COBOL, then C, then C++, then Java, now Ruby and friends) catches up to the available computing power and the available libraries are in a state where you can actually get something accomplished, there's a lemming-like urge to jump ship and start all over again in the latest and greatest ... with a less efficient, less optimized environment that pushes the performance barrier back up and out of reach.

Wait. Stop. Ask yourself "why?". As the differences between generations of languages become more and more subtle, you have to ask yourself ... what do I gain by switching? What cost do I pay? Am I throwing out the Baby with the Bathwater? Sure, you'll be incrementally more effective at doing the kind of things that look good in demos and tutorials ... but the moment you reach for your handy XML parser and it isn't there, or a friendly GUI toolkit and you have to start from scratch. Database access. Command line parsing. Transaction management. Yes, analogues exist for all of these, but you'll be starting from scratch to learn their APIS, quirks, bugs and workarounds. Just how compelling is that nifty bit of syntax, those couple of characters you don't have to type, that convenient built-in function?

For me, Java hits a terrific sweet spot. Vast numbers of open-source libraries. Very competent language. Minimal amounts of stupid syntax. Of course, there are things I'd change ... but in my analysis, its not about the language syntax its about how you use the language. I'm a framework guy; when I get annoyed at writing dumb, repetitive code I build a framework to take care of that dumb, repetitive stuff for me. I write it once, test it, forget how it works. Tapestry saves me a lot of trouble when dealing with the ugly intricacies of web applications. HiveMind saves me a lot of trouble when building complex apps from lots of simple little pieces. I don't throw out years of effort and hope some new language will solve my problems for me ... I face up to my issues and fix them right here, right now, in Java and be done with it.

Java is good enough. Sometimes "good enough" today is better than "ideal" tomorrow. I can get insanely great things done in Java today, and tomorrow, and the day after.

I'm not saying you should be complacent. Dave Thomas recommends learning a new language at least every year, to give you more experience, more insight into new ways of accomplishing things in your language of choice. I hope the Java language designers to the same, and give us some stuff we could use (such as continuations and closures). As a software craftsman, your technique should transcend any one programming language.

But back to Paul Graham; he's making that tired claim about productivity, as if productivity is linked to characters typed. So typing productivity trumps everything else? As you can see, I don't think so. I think he's fallen prey to a bad analogy ... that because some great hackers chose to use scripting languages such as Perl, Python and Ruby then all productive developers should use those languages for all chores, great and small. He's decided that one language (or class of language) is universally correct, and he's using that as a litmus test for being a great hacker.

Whereas, I think "the appropriate tool for the job", scripting languages for simple, scripting things. Power languages, such as Java, for long term, high quality, maintainable development. Individuals can move mountains, if they have the right tools. For the applications and frameworks I build, the right tools are Java, the Java runtime and the innumerable Java libraries.

German Tapestry Book Update

As mentioned previously, a German language book on Tapestry has been published ... and I now have a copy of it myself (thanks, Stefan!). Sorry, no review ... I don't read German, which means I can't even read my own vorwort (forward).

Saturday, July 24, 2004

Copland: HiveMind for Ruby

From the department of nifty!: Copland (named after the composer) is a Ruby version of HiveMind. Not just similar ... expressly based on HiveMind.

Wednesday, July 21, 2004

Groovestry?

Michael Henderson has been combining Groovy with Tapestry and has some pretty exciting results. He's not satisified with just listener methods in Groovy (using the little known <listener-binding> element) ... he wants page classes in Groovy, and eventually, he wants to eliminate page and component specifications and do those in Groovy as well. I can't think of a reason why not!

Tapestry Goes German

If you read German, you have something new to look forward to: Stefan Edlich and Patrik Kunert have written their own Tapestry book, subtitled Webanwendungen mit dem Apache Framework. A few more details are available at Software & Support Publishers' web site (which includes a downloadable chapter). Stefan emailed me to share his joy at holding a copy in his own hands, something I can relate to (was that only back in March?).

Sunday, July 18, 2004

HiveMind and autowiring

Sometimes the function of a community is to force you in the right direction. With HiveMind, there's the ongoing concern of too-much XML. Erik Hatcher somewhat forced the issue (he can be very incisive) and the response was the controversial Simple Data Language.

However, even that may not have been enough. The SDL is more concise, but your still have a lot to say in it. Over time, the BuilderFactory has been extended, in a number of ways to decrease the amount of SDL you need to put in place. For example, if a service needs to know its service id, it just needs a setServiceId(String) setter method and BuilderFactory will invoke it.

My more recent changes build on a very nice patch supplied by Marcus Brito, which allows you to access a service within the Registry using just its service interface. HiveMind will find the lone service point that implements that service and return it (throwing an exception if no service, or more than one service, implements the interface). Marcus did a good job, supplying a nice patch including JUnit tests (I only needed to do some minor touch ups). I can see how useful the approach is, though there's the danger of it causing problems (what happens at a later date when a second service is introduced?).

I've introduced another form of autowiring into BuilderFactory: after it has set all the properties it knows about (via autowiring and expicit sets) it checks the remaining writable properties. Any such properties that are interfaces are assumed to be services, and the unique service for that interface is located. Again, errors abound when the interface isn't a service, or there is more than one service implementing the interface (an attribute allows autowriting of services to be disabled).

The upshot of this is that you can specify less and less in the module deployment descriptor, and HiveMind will still do the right thing (or, it will provide detailed feedback about what went wrong).

The other big change in HiveMind of late is the concept of object providers. This is (as with much in HiveMind) somewhat abstract but very exciting. Previosly in HiveMind, the schema format identified the type and interpreation of attributes using translators. Is an attribute a configuration id? Then use the configuration translator. Is the attribute a service id? Use the service translator.

This is good as far as it goes, but it tends to promote a kind of propogation of elements. Look at BuilderFactory, which has a bunch of elements for different types: set, set-long, set-service, set-configuration and so forth.

This is also limiting; a property of an object (or service) to be set may be an interface, and it doesn't matter to the object whether that actual instance plugged in is a service or an arbitrary object. Given factory services (such as BeanFactory), its evident that HiveMind services aren't the only interesting objects out there. That's a problem that forces some people to cut-and-paste BuilderFactory to add new ways of supply objects to the constructed service. That's not good!

This is where the new object translator comes it. The translator is driven by a configuration point, ObjectProviders. The configuration defines different prefixes and provides services that know what to do with strings with that prefix.

So, for example, service:MyService will be intepreted as a service id, and bean:MyFactory:name will access a BeanFactory service and ask it for the name bean. There's even a service-property:MyService:propertyName option, to access a service and read a property from it.

For building services, BuilderFactory now has a set-object element. Further, since this is driven by a configuration point, it will be possible to add application-specific prefixes as well! So, once again, HiveMind is object soup, and its getting less important where those objects are coming from.

Saturday, July 10, 2004

Mozilla Gains on Internet Explorer

Slashdot is reporting that Mozilla Gains on Internet Explorer. I can say personally that I have switched entirely from IE to Firefox (0.9.1). Unlike the earlier version I tried (then named Phoenix), Firefox has been very fast, very stable and very ... fun. Just love the tabs.

I keep IE around for occasional compatibility testing, and for the couple of sites I use that don't like Mozilla, such as Shutterfly.

The only thing I miss is InstantSource, but I have contacted the authors about porting it over, and perhaps they will. I'm sure I'm not the only one. The WebDeveloper extension for Mozilla is really good, and has some great features that InstantSource doesn't have, but is not as useful as InstantSource.

Wednesday, July 07, 2004

Updated Tapestry In Action Examples

I just spent an hour or two updating the Tapestry In Action examples. This new source code distribution will be available from Manning's web site, in the meantime you can get it here (3.3 MB).

Changes:

  • Now works with and requires, Tomcat 5 (tested with 5.0.25).
  • examples.war renamed to tiaexamples.war, to not conflict with Tomcat's built-in examples.
  • No longer changes Tomcat's server.xml
  • Now includes the Tapestry 3.0 libraries and dependencies ... no seperate download needed.

Why include the libraries? Because it turns out that the exect version of the Tapestry framework is critical to getting the WAR to deploy. Initially I couldn't get tiaexamples.war to deploy and it took me forever to track it down; there's a reference to the Tapestry JAR in its web.xml:

  <taglib>
  	<taglib-uri>http://jakarta.apache.org/tapestry/tld/tapestry_1_0.tld</taglib-uri>
  	<taglib-location>/WEB-INF/lib/tapestry-3.0.jar</taglib-location>
  </taglib>
If that JAR doesn't exist (say because you tried to deploy using 3.0-rc-1) then the WAR just doesn't deploy. You have to dig through the Tomcat logs for a while before you find the problem. The best solution was to ensure that the version matches up by including all the libraries and dependencies (getting the dependencies being every newbies #1 issue). Because this distribution isn't served off of apache.org or a mirror, including the dependencies is completely legitimate.

Sunday, July 04, 2004

HiveMind and automatic reloading

I gave this another thought yesterday while relaxing on the beach. HiveMind doesn't have a way to redeploy because a descriptor changed. That wasn't on e of the problems I wanted to solver with HiveMind initially.

It's a complicated problem, because the very nature of HiveMind is to intertwine services. Service A may use configuration B that includes a bean obtained from service C that is itself driven by configuration D. If a contribution to service D changes, how does that affect service A? I suppose, in theory, you could do some tricky forward chaining so that a change to configuration D will be identified a propagated forward, invalidating service C and configuration B and ultimately service A. You could then have the proxy for service A somehow obtain a new instance that reflects the change to configuration D.

What are you willing to pay for that? The CPU and memory expense of determining what has changed and what is affected looks prohibitive to me. Something would have to poll for changes to the modules, re-parse the module descriptors, rebuild the various module descriptors, and send notifications to proxies and other objects.

The thing is, HiveMind is supposed to be light and fast. I have always pictured HiveMind running inside an EAR. When the EAR is redployed, the old HiveMind Registry is shutdown and a new HiveMind Registry is created and started to replace it. During development, just bounce your application (i.e., restart Jetty). I'm doing this today on my current project, and it's not a great imposition ... just a couple of seconds to stop and restart. The Jetty Launcher plugin makes it a single click.

So I'm still filing this under YAGNI(You Aint Gonna Need It).