On and off I have looked at messaging for Java applications as an alternative, more loosely coupled design pattern. Particularly for smaller applications messaging has some interesting solutions in lieu of other approaches.
Take the event listener model for AWT/Swing. Each component is responsible for maintaining its own listener event list, and each listener is responsible for finding the component it wants to get events from. This is, of course, the Observer pattern.
I want more decoupling. Message busses decouple this pattern by managing listeners and delivering events—I grew up calling this publish/subscribe—mixing in the Mediator pattern models the bus.
Channels and Subscribers
I'm going to call the bus a channel. There are many common vocabulary choices for the kind of simple message bus I have in mind, and channel is the one I used most with Gregory Hohpe who introduced me to this design. Start with the channel interface:
public interface Channel {
void subscribe(Object subscriber);
void unsubscribe(Object subscriber);
void publish(Object message) throws Exception;
}
Choices, choices. Most messaging interfaces I see use a topic text to distinguish messages when publishing. But like the AWT/Swing event system, I prefer objects so that I may use inheritance to narrow or widen the scope of events I receive. Subscribers receive any intersection or union of message types they want using simple method overloading. For example:
interface BobTheBuilder {
void onMessage(CanHeFixItRequest message);
void onMessage(YesHeCanDispute message);
}
class MessageLogger {
public void onMessage(Object message) {
SomeLogger.logMessage(message);
}
}
MessageLogger
subscribes to all possible message types planning to ambitiously log everything. BobTheBuilder
is only interested in requests to fix something or in criticisms of his abilities.
Supporting the union of disjoint message types explains why subscribe(Object):void
and unsubscribe(Object):void
take object arguments rather than using a Subscriber
interface. Sketch out what Subscriber
might look like with typed messages:
interface Subscriber<T> {
void onMessage(T message);
}
This works great is a subscriber wants a single type of message, but Java does not support classes implementing the same interface multiple times—generics does not produce different interfaces:
interface IllegalJavaSubscriber {
implements Subscriber<String>, Subscriber<Integer> {
// Will not compile.
}
And the lack of typed interfaces for subscribers implies that the signature for onMessage(Object):void
cannot narrow either. What to do? The only option is reflection. So I reflect for "onMessage" methods with the correct signature and handle overriding and overloading in the bus.
All in all I get a lot of flexibility for very little work by subscribers using the bus.
Exceptions
But what of exceptions? I realized, given the inheritance of message types, that I could simply publish exceptions as messages, same as any other type. Exception handlers simply subscribe to messages:
class ExceptionLogger {
public void onMessage(final Exception e) {
SomeLogger.logException(e);
}
}
This is akin to how AOP works but without the extra syntax or new language to learn. The try/catch normally in each subscriber for logging exceptions is gathered together into the message bus with pseudo-code akin to:
for (final Subscriber subscriber
: getSubscribersForMessage(message))
try {
invokeOnMessage(subscriber, message);
} catch (final Exception e) {
this.publish(e); // I am a channel
}
This simplicity is very appealing, but it leaves out an interesting possibility: that subscribers could try redeliving a message to just the failed cases. Adding one more reflected method adds this option:
class SystemMonitor {
public void onMessageException(Exception e,
Object subscriber, Object message) {
fixSystem(); // magic
retryMessage(subscriber, message); // more magic
}
}
In principal a subscriber could republish using the signature after fixing some broken part of the system. It would also make logging more informative.
Active Messages
Using pure reflection opens up another design choice, active messages. Simply having messages themselves provide an onMessage
method and subscribe to the channel. Messages can publish themselves. As clever as this seems, it does leave me in fear of maintaining someone else's system years hence in which the entire thing is a ball of spaghetti with messages and subscribers calling hither, thither and yon.
Debugging is a problem with systems like this. YMMV.
Code
Some simple code illustrating this post.