Wednesday, July 26, 2006

Metaprogramming Java with HiveMind

Holy time management, Batman! I'm going to get a bad rep at OSCON for running over time (last year, though, it was due to a late start).

Basically, I was pacing myself for a 60 minute session, not the 35 they give you. Way too many slides, too much intro, kept me from the cool stuff at the end of the session. Also, there were lots of good, informed questions.

My goal for the Tapestry session, later today, is to run under. But I don't see that happening.

Tapestry for PHP?

PRADO is a PHP framework expressly inspired by Tapestry. As you might expect, all I've had a chance to do is glance over the documentation, but it looks very nice, and their site is very professional and slick.

I suspect the relationship between PRADO and Tapestry is the same as between Tapestry and WebObjects ... the precursor "proves" the space is viable, but I doubt the implementation is all that similar, given the different languages involved.

Still, best of luck ... but don't expect any framework, on any platform, to keep up with Tapestry 5!

Monday, July 24, 2006

Tapestry 5 Updates

Even during OSCON, I've been churning out code for Tapestry 5.

The new Tapestry IoC container is rapidly coming together. The idea of replacing XML with Java classes (and naming conventions and annotations) is working out wonderfully. I'm busy putting together a version of configurations about now.

What I'm finding, as I code and also predict how the final Tapestry code will use the container, is some steep improvements.

In Tapestry IoC, injection isn't into your beans via property setters or constructors, the way it is in HiveMind and Spring. Instead, injection is via parameters to your builder methods. Injection via method parameters will also occur into decorator methods (the replacement for service interceptor factories) and into contribution methods (which contribute values into service configurations).

For example, in Tapestry IoC:

package org.example.myapp.services;

import org.apache.tapestry.ioc.annotations.Id;

@Id("myapp")
public class MyAppModule
{
  public Indexer buildIndexer()
  {
    return new IndexerImpl();
  }
}
The above defines a service "myapp.Indexer" as an instance of IndexerImpl. The equivalent in HiveMind would be :
<module id="myapp" version="1.0.0" package="org.example.myapp.services">
  <service-point id="Indexer">
    <create-instance class="IndexerImpl"/>
  </service-point>
</module>
That's already an improvement. By the time you start talking about dependencies, things get even better:
  public Indexer buildIndexer(String serviceId, Log serviceLog, 
    @InjectService("JobScheduler") JobScheduler scheduler, 
    @InjectService("FileSystem") FileSystem fileSystem)
  {
    IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem);
      
    scheduler.scheduleDailyJob(serviceId, indexer);

    return indexer;
  }

What's worthy of note here is how dependencies get injected in (as method parameters), and that lifecycle concerns about the IndexerImpl are separate from its implementation. We don't have to inject the scheduler into IndexerImpl in order for the Indexer to be scheduled by the scheduler ... that concern can be implemented only in the builder method.

To accomplish that kind of separation in either HiveMind or Spring would require quite a bit of hackery; say, creating a new, specialized kind of ServiceImplementationFactory (in HiveMind) that understood about talking to the scheduler. Lots of work for something that can be expressed so easily in a short, simple, testable Java method.

I think that we'll see this approach bear fruit in the form of fewer services to accomplish the same goals. It will allow for non-service objects to easily receive injected services ... such objects can be created "on the side" by builder methods (or contributor methods).

This is the theme for all of Tapestry 5: Simpler, easier, faster, more understandable, more powerful. Avoid XML. Improve productivity. Make the framework adapt to your classes and your methods, rather than the other way around.

Maven Thoughts

As much as I disliked Maven 1 , I've come to enjoy and rely on Maven 2.

It's getting things done for me. It's fast. The Maven plugin for Eclipse (that is, an Eclipse Plugin that support Maven, rather than the Maven plugin that generates Eclipse control files) seems to work well, automatically picking up changes to my pom.xml, as well as automatically downloading dependencies. And I'm using it in a tough way, given that I'm also using AJDT on the same projects.

I also think that the new "almost plain text" format is a godsend; it makes it much eaiser to quickly assemble good documentation. I'm trying to write documentation before writing code for Tapestry 5.

Still, maybe 20% of the time, I don't feel that I'm using a tool so much as appeasing a petty god. There's still a number of things I can do easily in Ant that seem to require writing Maven Mojos and plugins to do in Maven.

Sunday, July 02, 2006

Synchronization Costs

I've been doing a bit of work on the Tapestry 5 code base. I'm really interested in making Tapestry 5 screaming fast, and since the code is based on JDK 1.5, we can use concurrency support. Previously, I've blogged about using an aspect to enforce read and write locks. I decided to write a simple benchmark to see what the relative costs were.

As with any benchmark, its only an approximation. I tried enough tricks to ensure that Hotspot wouldn't get in there and over optimize things, but you can never tell. HotSpot is a devious piece of software.

I got interesting, and strange, results:

For a base line, I executed the code with no synchronization whatsoever (simple). The cost of synchronization (synched) shows that synchronization is pretty darn cheap, just an increment on top of the baseline code. The aspect graph shows the cost of using the @Synchronized aspect to maintain a reentrant read/write lock (that is, shared read lock combined with an exclusive write lock). Finally, the rw graph shows the cost of writing code that maintain the read/write lock in normal code (rather than having it added via the aspect).

Synchronization has some overhead. Using the @Synchronization aspect is about 4x as expensive as just using the synchronized keyword on a method. Strangely, the aspect version operates faster than the pure code version for reasons I can't explain, except it must have something to do with how AspectJ weaves my code (a lot of code I write ends up as private static methods after weaving, which may have some runtime performance advantage).

These results demonstrate an important tradeoff: if your application only occasionally has multiple threads hitting the same methods, then you might want to choose synchronized, since you aren't in danger of serializing your threads. By serializing, we mean that only one thread is running and all other threads are blocked, waiting for that thread to complete a synchronized block. Serialized threads is what causes throughput for a web site to be bad, even though the CPU isn't maxed out ... it's basically, Moe, Larry and Curly fighting to get through a single, narrow door all at the same time (they race to claim the single, exclusive lock).

Tapestry, on the other hand, will have a number of choke points where many threads will try to simultaneously access the same resource (without modifying it). In those cases, a shared read lock (with the occasional exclusive write lock) costs a little more per thread, but allows multiple threads to operate simultaneously ... and that leads to much higher throughput. Here, Moe, Larry and Curly get to walk through their own individual doors (that is, each of them has a non-exclusive read lock of their own).

As with any benchmark, my little test bench is far, far from a simulation of real life. But I think I can continue to make use of @Synchronized without worrying about tanking the application. In fact, just as I predicted Tapestry 4 would out-perform Tapestry 3, I believe Tapestry 5 will out perform Tapestry 4, by at least as much.