GitHub   @OpenKivaKit Twitter Zulip Chat RSS Feed Java Code Geeks Mailing List

State(Art)

Jonathan's thoughts on the state of the art in software design.

2021.07.07

Kernel - Messaging - How messaging improves component semantics  

Dr. Alan Kay’s conception of object-oriented programming in the late 1960’s came about, in part, as a result of his undergraduate work in molecular biology. A cell is a pretty good analogy for an object and DNA is a sort of template or class from which cells are created. But to Kay, what would have been most interesting were cell surface receptors, and the way that cells pass various kinds of messages to each other (and themselves) by secreting compounds that bind to these receptors. He posted this revealing email in 1998:

[…] Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” […]

Which brings us to the Broadcaster / Listener design pattern. A language like Java, with statically bound, synchronously invoked methods, is not at all what Dr. Kay means when he talks about messaging. But even if Java isn’t a dynamic, late-bound, messaging-oriented language, we can still do some interesting messaging in Java. The Broadcaster / Listener design pattern is one way to do it, and it turns out to be very useful and powerful.


Broadcasters

A Broadcaster is a Transmitter that transmits Messages to an audience of one or more Listeners. Here are a few of KivaKit’s most commonly used messages:

Message Purpose
CriticalAlert An operation failed and needs immediate attention from a human operator
Alert An operation failed and needs to be looked at by an operator soon
FatalProblem An unrecoverable problem has caused an operation to fail and needs to be addressed
Problem Something has gone wrong and needs to be addressed, but it’s not fatal to the current operation
Glitch A minor problem has occurred. Unlike a Warning, a Glitch indicates validation failure or minor data loss has occurred. Unlike a Problem, a Glitch indicates that the operation will definitely recover and continue.
Warning A minor issue occurred which should be corrected, but does not necessarily require attention
Quibble A trivial issue that does not require correction
Announcement Announcement of an important phase of an operation
Narration A step in some operation has started or completed
Information Commonly useful information that doesn’t represent any problem
Trace Diagnostic information for use when debugging

        

A Broadcaster keeps a list of Listeners (the audience) to which it will transmit messages. Listeners can be added and removed from the list. When the transmit(Transmittable) method is called with a message, that message is given to each member of the audience:

public interface Transmitter<T extends Transmittable>
{
    void transmit(T value);
}

public interface Broadcaster extends Transmitter<Transmittable>
{
    void addListener(Listener listener);
    void removeListener(Listener listener);
}

Note: By default, a broadcaster with no listeners will log any messages it hears, along with a warning that the broadcaster has no listeners. The system property KIVAKIT_IGNORE_MISSING_LISTENERS can be used to suppress these warnings for applications where this is acceptable.


Listeners

A Listener receives messages sent to it with its receive(Transmittable) method. Different implementations of this interface may do different things with the message:

public interface Listener<Transmittable>
{
    default <T extends Broadcaster> T listenTo(T broadcaster)
    {
        broadcaster.addListener(this);
        return broadcaster;
    }
   
    void receive(Transmittable message);
}

Here are a few examples of the many Listeners in KivaKit, just to give an idea of the range of tasks that listeners can perform:

Listener Purpose
Logger Write the message to a Log
MutableCount Counts the number of messages it receives
MessageList Keeps a list of the messages it receives
ThrowingListener Throws an exception containing the message
ConsoleWriter Formats and writes the message to the console
StatusPanel A Swing panel that displays messages it receives
NullListener A listener that discards the messages it receives
ValidationReporter Reports validation problems it receives


Repeaters

A Repeater is both a Listener and a Broadcaster. When a repeater hears a message via receive(), it re-broadcasts it with transmit():

public interface Repeater extends Listener, Broadcaster
{
    @Override
    default void receive(Transmittable message)
    {
        transmit(message);
    }
}

This process of listening and re-broadcasting forms a listener chain:

broadcaster -> repeater -> listener

A repeater can, of course, listen to other repeaters, forming longer listener chains:

broadcaster -> repeater -> repeater -> repeater -> listener

For example:

class EmployeeLoader extends BaseRepeater
{
    [...]
     
    problem("Unable to load $", employee);

    [...]
}

class PayrollProcessor extends BaseRepeater
{
     [...]

     var loader = listenTo(new EmployeeLoader());
     
     [...]
}

var processor = LOGGER.listenTo(new PayrollProcessor());

Here, if the EmployeeLoader class can’t load employees, it reports a problem via the problem() method. The problem() method in BaseRepeater broadcasts a Problem message to all listeners of EmployeeLoader. The PayrollProcessor class creates and listens to an instance of EmployeeLoader. Since PayrollProcessor is itself a repeater, any messages that the EmployeeLoader broadcasts to PayrollProcessor will be re-broadcast by PayrollProcessor to each of its listeners.

The final line of code listens to messages from PayrollProcessor with a Logger. So if something goes wrong loading employees, the problem message goes from EmployeeLoader through PayrollProcessor to Logger, where it gets logged:

EmployeeLoader -> PayrollProcessor -> Logger

This design yields a lots of flexibility as well as clean, simple error handling with very little code.

A class can implement the Repeater interface in two ways. It can extend BaseRepeater (as above) or it can implement RepeaterMixin if it already has a base class. For more details on how mixins work in KivaKit, see How KivaKit adds mixins to Java.

The ability to arbitrarily chain messages, particularly messages that represent some kind of warning or problem, greatly increases component flexibility:

KivaKit has hundreds of classes that are Repeaters. This allows most non-trivial components to be chained together with ease. A short, randomly selected list of classes in KivaKit that are repeaters:

Repeater Purpose
Converter Convert one type to another
Resource Provides access to streamed resources
EmailSender Sends emails
Application Base application class that logs messages by default
ConnectionListener Listens for server connections
JarLauncher Downloads and launches a JAR file

Each of these classes listens to errors from components it uses and broadcasts problems to its own listeners.


Code

The simplified messaging code discussed above is available in its entirety in kivakit-kernel and is used throughout KivaKit for messaging between components:

<dependency>
    <groupId>com.telenav.kivakit</groupId>
    <artifactId>kivakit-kernel</artifactId>
    <version>${kivakit.version}</version>
</dependency>


Questions? Comments? Tweet yours to @OpenKivaKit or post here:


Copyright © 2021 Jonathan Locke