« Day 2, large Java heaps | Main | Will 64 bit require in memory databases? »

March 02, 2007

The listener pattern evolves

This is as much an art as a science but one of the biggest pain points is callbacks. Most middleware components need to make callbacks to an application registered listener. Traditionally, these listeners are objects that implement an interface provided by the middleware. This is a major design issue for the following reasons. When you need to add a new event, you can't just add a method to the interface as this will break all existing applications. The next thing is to make a new interface that extends the old one. New listeners that need the new event implement the new interface. This is an ugly approach though. I don't care for it but you had little option up to now. There are variations on this such as not extending and then checking if the listener implements the new interface etc. This approach has been in use since Microsoft COM and before.

Another problem with this approach, besides its lack of extensibility, is performance. You need to be very careful with the event methods on a single listener. If you mix high frequency events with low frequency events then when every someone adds a listener for a low frequency event then you're adding performance loss invoking the high frequency events. The listener catches all events and if they need one event then you are forced to deliver all events on the interface. This can be a major performance issue. I made this mistake with the ObjectGridEventListener in ObjectGrid. This interface has methods for lifecycle (very low frequency events) and methods to start and end transaction (high frequency events). If someone wants to receive the lifecycle events then they need to add this listener. But, in doing so, ObjectGrid now calls them twice for each transaction and clearly the application doesn't want or care about this event. It's a performance loss. You can work around it by using bit flags indicating which methods the application is interested in when the listener is added but this is ugly. You can try seperating events with similar delivery rates in to seperate interfaces but again, this is ugly.

So, whats the solution? Clearly, these approaches are not state of the art, not extensible, and can cause poor performance when you're not thinking straight one day. Annotations offer another way of handling events and this is the way ObjectGrid will be doing it from now on.

This isn't new, I've seen this in a couple of places now. The basic idea is to allow the application to simply register an object, no interfaces or base classes. The object methods can be called anything but each event has a certain signature, for example, an initialize method might be forced to have a signature like:

void XXX(ObjectGrid grid)

The method is then annotated to indicate which event should invoke the method. Example

@Initialize void someMethodName(ObjectGrid name) { ... }

If the destroy event is required then add a method like this

@Destroy void someOtherMethodName(ObjectGrid name) { ... }

You get the idea. The annotation indicates which event should invoke this method. The method signatures are standardized, we don't care what the method name is. This solves the issues with the older approach nicely.

New events can be easily added without breaking existing listeners and without requiring new interfaces etc. Simply, make a new annotation public and then check for methods with that annotation in ObjectGrid.addListener.

Performance is also better as we know only call exactly the methods the application is interested in. Whereas before, we had no choice for to call all methods on the interface whether the application wanted the events or not. There is a slight performance hit in terms of requiring reflection to invoke the method but this overhead is small on the newer JVMs, I think it's a good trade off.

So, this is the strategy now. We're implementing new events in ObjectGrid 6.1 using this approach. This means if an application wants the new events then they need to use Java 5 or better. I'm not going to provide an xml file or something allowing the method to be specified on 1.4.2 or lower JVMs. I'm trying to get rid of configuration as much as possible so adding another xml is not the goal and Java 5 is now pretty common. It's a non issue for J2SE applications, BEA, JBoss, Tomcat, Geronimo, WebSphere CE etc using ObjectGrid and WebSphere 6.1 now uses Java 5 also so thats the plan. If you want the new events, Java 5 or 6 is the answer. If you use Java 1.4.2 or lower, you can't get the new events.

I'm hoping this approach with greatly help with extensibility when new events need to be added, it avoids the mixing event frequency issues and allows only the events needed by the application to be delivered to it. So, it's perfect? Nope, interfaces were nice from a documentation point of view. Group similar methods together and java doc them. It's not so convenient to group annotations together besides a package for each group of events. That what I'm looking at doing in ObjectGrid. I don't see another way to do it. Regardless, this looks like a better approach and so far looks nice in samples etc.

This approach is being used in most open source stuff and the EJB 3.0 spec has the same stuff on interceptors etc. That where I'd seen it before. I was worried about compatiblity with Java 1.4.2 or lower up to now but you need to start competing with the other stuff that doesn't care about this aspect so this is the time I guess. We'll start trying to exploit as much of the Java 5/6 features as possible to make the stuff easier to use moving forward starting with 6.1

March 2, 2007 | Permalink

Comments

More on this topic. http://devwebsphere.com/devwebsphere/2007/03/the_listener_pa_1.html

Posted by: Billy | Mar 3, 2007 2:35:23 PM

Post a comment