Overview

A recent thread on the Usenet newsgroup comp.object discussed replacing switch logic with polymorphism in a message processing system. My suggestion was to use the Chain of Responsibility pattern (Design Patterns) in order to decouple the creation of a concrete message instance from the message broker communication logic. The goal is to allow the addition of new message types without modifying the message broker, adhering to the Open Closed Principle.

Java Implementation

I implemented this in Java with just a handful of classes:

Two example message handlers are also included:

Caveats

This is example code, not production code. Synchronization and thread pooling, for example, are not used. Error handling and recovery is minimal at best. No scalability or performance testing has been done. Message handlers can only be added, not removed.

Usage

After compiling the source, the server can be started with the command:

java org.softwarematters.example.MessageBroker.MessageBroker

The default port is 8079; an alternative can be passed on the command line.

MessageBroker's main method bootstraps the server by adding the AddHandlerMessageHandler. This allows additional handlers to be added dynamically. To demonstrate this, connect to the server with:

telnet localhost 8079

(or whatever port was specified when MessageBroker was started) and send the string:

CONTROL.ADD_HANDLER org.softwarematters.example.MessageBroker.EchoMessageHandler

Reconnect via telnet and send the string:

ECHO foo bar baz

You should see foo bar baz echoed back.

Implementation Choices

Dynamic Registration of Message Handlers

In this implementation, new MessageHandler subtypes are registered by name. The MessageBroker passes the name to the MessageHandlerFactory where a Class of the appropriate type is created, if possible. There are a large variety of alternative implementations that meet the same design goals, including:

  • Reading class files from a known directory.
    The MessageHandlerFactory would monitor this directory for changes and register new MessageHandler subtypes when they appear.
  • Registration of a proxy object.
    Each concrete MessageHandler subtype would connect to the MessageBroker and register a proxy. The MessageBroker would provide a separate port for these commands. The proxy could either contain all of the functionality required for message handling or could dispatch messages to a separate process.
  • Jini.
    Sun's Jini network technology is a powerful architecture that enables the creation of distributed systems that are highly adaptive and supportive of change.
Many other implementations are, of course, possible.

MessageHandlerContext

The MessageHandlerContext class is a bit of a hack, in the sense of building furniture with an axe rather than something with hack value . Concrete subtypes of MessageHandler are, of necessity, coupled to the MessageHandler abstract base class. The use of MessageHandlerContext couples those subtypes to two other classes in the same package, namely MessageHandlerContext and MessageBroker. Not all message handlers will require all, or even any, of the context information provided. On the other hand, the Reuse / Release Equivalence Principle and related granularity guidelines suggest that this isn't a huge problem. Still, in a production system I'd work harder to eliminate this coupling.

Common Lisp Implementation

For a different view of the same concepts, I implemented the message broker in Common Lisp, still using the Chain of Responsibility pattern to allow dynamic addition of new message types. All the source is in the file message-broker.lisp.

Caveats

As with the Java implementation, this is example code, not production code. Error handling is minimal and the code hasn't even been compiled (yes, Common Lisp is a compiled language). Finally, the code is dependent on the SBCL networking and threading packages.

Usage

Start SBCL:

sbcl

Load message-broker.lisp:

* (load "message-broker.lisp")

This will start the server in its own thread with only an echo message handler registered. To demonstrate this, connect to the server with:

telnet localhost 8079

and send the string:

ECHO foo bar baz

You should see foo bar baz echoed back.

To add a new handler, change to the appropriate package and call add-handler from the SBCL prompt:

* (in-package :org.softwarematters.message-broker)
* (add-handler *message-broker* #'rot13-message-handler)

Reconnect via telnet and test this handler by sending the string:

ENCRYPT.ROT13 foo bar baz

You should see sbb one onm echoed back.

To stop the server, quit out of SBCL:

* (quit)

Implementation Choices

It would easily be possible to create the classes of the Java implementation directly using the Common Lisp Object System (CLOS), but such a direct translation wouldn't take advantage of Common Lisp's advantages. Peter Norvig described how many standard design patterns are unnecessary or transparent in languages such as Common Lisp . The Java MessageHandler class is an example of the Strategy pattern, used because Java lacks proper closures . Functions are first class objects in Common Lisp, eliminating the need for both an explicit MessageHandler class and its associated factory. Instead of creating concrete subtypes of a MessageHandler class via a MessageHandlerFactory, this implementation allows message handler functions to be registered directly with an instance of message-broker.

If more complex behavior were required, a message-handler class could, of course, be used. The factory class would still be unnecessary.

Summary

The code here shows how to implement the Chain of Responsibility pattern to allow the behavior to be added dynamically existing, running system.

The most interesting result of this exercise for me is the significant difference in the amount of code required for the Java implementation compared with the Common Lisp implementation. MessageBroker.java alone has nearly twice the SLOCS of all of message-broker.lisp. If this difference remains when both implementations are extended to production quality in terms of performance and resiliency, it strongly suggests that Common Lisp is significantly more expressive and powerful than Java.

Please contact me by email if you have any questions, comments, or suggestions.

www.softwarematters.org