Message Routing using Double Dispatch with C#

This post describes message routing inside an application. It does not describe message routing across message brokers or across systems.

Message driven, or push based notification, systems stream Messages from message sources to interested parties (Observers).  There are often multiple message types routed to multiple observers.  Message types are represented in code as Message interfaces/classes.

Message Observers are often interested in some subset of the messages and implement some type of interface or registration scheme that tells the message routing module which message types they are interested in.

Scenarios

One use case for this is a User Interface that streams UI events (Messages) from various components to event handlers (Observers).  The message sources create messages specific to that event type. The event handlers may receive and process messages of one or more types.

Another use case might be some type of IoT device like an IOIO that creates events at scheduled intervals or based on some type of external input. Each change event has its own message type.  Components that have interest in some subset of the messages can subscribe for notifications of those types of messages/events.  The dispatcher notifies the interested subscribers every time messages of the correct type appear.

Complexity

We want to take messages of various types and route them to handlers without losing the typing information and without resorting to casting in the handlers . This means we'd like to bind message types to some kind of Observer Notify() method that accepts that exact message type or the next closest abstract version.  This is similar to the MVC or other models where inbound web requests are routed to controller methods that have a signature that matches the inbound object type as closely as possible.   

Message subscribes should be simple with any dispatch complexity hidden in the messages or dispatcher.  We'd like to make it easy to add additional message types and support their associated observers.  This means we want to avoid large God classes with giant case statements that identify messages by types and route based on specific message signatures. 

Dynamic Dispatch and Double Dispatch

Double dispatch pushes message type to observer method binding out of the core dispatch (or receiver) to the messages themselves. It is similar to a Visitor pattern.


  1. Observers register interest with the message receiver. Observers implement Observer() methods based on the message types they are interested. Dynamic dispatch will determine if an observer is interested in a specific message type at runtime. An observer can be interested in more than one message type.  
    1. I'll use the Microsoft Observer methods here.  Ex: OnNext(GameStart), OnNext(GameEnd). You could use other interfaces like Visit or Dispatch.
  2. A message is generated and posted to a message queue.
  3. The message dispatcher receives the message. The receiver may be listening on some message stream or queue.
  4. The dispatcher iterates across each observer.
    1. Each observer is Dispatched to Message.
    2. The message calls the Observer's  OnNext() method that is most closely typed to the message type.


Implementation Example

Message Types

The Interface diagram to the right shows the Business message hierarchy. No special API or behavior is specified here.

All message interfaces are subclassed off.  All the IMessage types other than IMessageBase are implemented by concrete classes.
IMessageNotification isn't shown here. It is a notification support interface used as part of the double dispatch process.

Concrete message classes implement two interfaces

  1. he business interface that contains the message properties and any special behavior.
  2. The message notification interface used for observer dispatch.   The dispatcher/router invokes the IMessageNotification interface one time for each registered observer.


Observers

Observers implement two interfaces, a non-genericized IObserver  and a genericized IObserver<MessageType> that expresses interest in a specific message type or its sub types. IObserver is a marker interface.  IObserver<MessageType> is the dynamic dispatch interface. Observers implement the IObserver<MessageType> interface one time for each message type they are interested in.

Observers register for notification using the Subscribe(IObserver) method modeled on IObservable. They do not register which message types they are interested in. This will be determined at runtime by the Message's dynamic dispatch code.



Dispatch Mechanics

The dispatcher/message receiver receives messages as they come in. It invokes the message's  Dispatch(IObserver) one time for each observer.  The Dispatch(IObserver) method tries to find a IObserver<MessageType>  method on the Observer that can receive this message type. It then calls Dispatch<IObserver, MessageType) on itself.  That method then invokes  OnNext<MessageType> on the Observer.  It invokes the OnNext<MessageType> for the message type that is closest to the the type of the message doing the processing. 

Example Code


...begin source code... ...end source code...

Notes

Some of the pattern in here came from this stack overflow posting
Wikipedia has good descriptions of Multiple Dispatch and Double Dispatch

This is one of the core patterns in Smalltalk. I'm just saying this so Smalltalker's don't flood me with email telling me how they've been doing this since the 80s.

This post created 4/2015

Comments

Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs