I recently gave a presentation at a local user group on message transport abstraction and how it can be used to abstract communication between actors running in LabVIEW and thin clients running in web browsers. During the presentation an interesting question came up regarding why I chose to make communication between my actors data centric instead of message centric. This question triggered a lot of thinking on my part and after a few days of rumination I decided that a blog post on the topic was warranted.
During the presentation I held a mini code review during which I showed a version of a simple actor that used a mediated message bus to communicate with other actors through abstracted message transports. In my implementation actors register as publishers and subscribers of data on the message bus and their actions are driven by data arriving in their inbox. I pointed out the subtle difference between this data centric implementation and the Actor Framework, in which messages typically map to actions being requested from Actor to Actor. The question raised was along the lines of, "What benefit does this implementation have over how the Actor Framework does it?". It is a good question and I am going to attempt to provide a good answer below.
Before diving directly into the technical details let me preface the discussion by defining what I mean by data centric communication and message centric communication within the context of actor oriented programming. In this discussion I will refer to actors (lower case "a") in the general sense as a system of concurrently running software modules that receive messages and perform some kind of computation on them. There is a well known framework written by National Instruments known as the Actor Framework (upper case "A") that implements the actor model and I will be comparing Actor Framework Actors to my own implementation of actors.
In general actors communicate with each other by sending asynchronous messages that are stored in mailboxes until they are processed. This discussion focuses on two different ways to structure the messages that are sent between actors. We will first look at how the Actor Framework uses the command pattern to send message centric messages between Actors, where each message typically maps to an action that one Actor wants another Actor to perform. We will then look at my actor implementation which uses the mediator pattern to send data centric messages between actors, where each message typically maps to a specific data type. This discussion will focus on how the difference between data centric and message centric communication impacts the overall design of an actor oriented program.
The Actor Framework
If you are already familiar with how the Actor Framework handles messaging between Actors then feel free to skip ahead to the next section. As a note, I decided not to discuss the low-coupled messaging pattern that can be used to communicate in a more loosely coupled fashion between Actors, mainly because I discussed it in detail in this blog post.
Let's start by looking at how the Actor Framework handles messaging. The image above is the block diagram for Actor Core.vi, this block diagram is essentially a typical consumer loop where the queue is replaced by the "Priority Queue" object and the case structure is replaced by the Do.vi, which is nested within the Receive Message.vi.
The Do.vi's job is to take incoming messages as they are dequeued from the Priority Queue, and dynamically dispatch based on the message class type to run the appropriate method of the Actor. This dynamic dispatching on the Do.vi is an implementation of the Command Pattern, a well known behavioral design pattern.
Note that the Do.vi is a method of the message class not the Actor, and that when the Actor dequeues a message object it will dynamically dispatch on the message classes Do.vi within Actor Core.vi. Each Do.vi calls one of the Actors methods and this is how the Actor does work. For a deep dive on low coupled messaging in the Actor Framework see this post.
One thing I want to point out is that the Do.vi has an Actor.lvclass object as an input and that within the Do.vi the Actor.lvclass is cast to a more specific child (Beta.lvclass in the image above). Once the to more specific class has cast the object type to Beta.lvclass one of Beta.lvclass's methods can be called, in this case it is the Update Data.vi. This messaging scheme tends to necessitate one message class for each Actor method that needs to be invoked by message.
With the command pattern based mesaging scheme the messages tend to map to actions or commands. The Actor Framework's use of the command pattern makes the message itself the primary means of interaction between Actors. With this message centric approach developers write applications that focus on sending messages between Actors.
Mediator Pattern Actors
Now let's take a look at the mediated message bus actor implementation I was presenting on at the LVUG. This implementation uses the mediator pattern as a publish-subscribe mechanism for passing data between actors (for more information see these blog posts: Get/Set Control Values by Index, Publish/Subscribe with the Actor Framework, and You've Got Mail). The mediator acts as a message bus that allows actors to register anonymously as publishers and/or subscribers of data. In order for a publisher actor and a subscriber actor to communicate they have to agree on two things; the name of the data and the type of the data.
Topics provide the basic connection between publishers and subscribers. Each Topic has a unique name and a data type. The data type is a subclass of a Message class where each child maps to a particular data type.
Notice that these messages are not command pattern messages, instead of mapping to an action each message simply represents data. Because the data messages are classes more complex data types can be introduced as children of the data Message Interface. Each data message has a key that acts as a name for the data in the message. As an example, a Topic for temperature data could be created with the name Temperature and the type Message Double.lvclass. All temperature data would be published to this Topic. As data points from individual sensors are published the name of the sensor could be included as the key for the message. This makes the application scalable because new topics don't need to be created as new temperature sensors are added to the system.
The image above shows the actor core for a simple mediator actor. In this case the actor has registered as a subscriber to a Topic named Broadcast, which handles all temperatures in the system. When a message comes in from the Broadcast Topic the actor handles it by casting the message class to the expected type (in this case Message Double.lvclass) and then calling the appropriate method to act on the data.
Notice that the command pattern is not in use, the message does not contain any information about an action to be performed when the data is delivered, it simply delivers the data. In the example above the "Reactor Temperature", "Precursor Temperature", "Stop Valve Temperature", "Cone Temperature", and "Plasma Temperature" cases in the case structure all call methods that use the Message Double.lvclass data type meaning that there are 5 actor methods using 1 message class. Compare this to a command pattern implementation that would necessitate the creation of 5 message classes, one for each actor method.
Notice that the to more specific type cast happens on the data message and not on the actor as it did within the Do.vi of the Actor Framework. The Actor Framework is message centric and so actors need to be cast to ensure that they can handle the specific message, where as the mediated actor is data centric and so data messages need to be cast to ensure that they are of the type that the actor expects. This is a subtle yet important difference.
Now that I have provided context for both message centric and data centric communication let's try to answer the original question, "What benefit does this implementation have over how the Actor Framework does it?".
Message centric communication usually sends "verbs", while data centric communication usually updates "nouns". In a verb-based system, actors interact directly with each other. Actor A sends an Update Reactor Temp message to Actor B, where the payload of the message is the reactor temperature. In a data centric noun-based system actors interact with the data, not directly with each other, which reduces coupling. Actor A broadcasts a new reactor temperature to the temperature Topic and if actor B is subscribed to the Topic it will receive the new temperature. Verb-based systems need many commands and actions, and this set grows with the number of actors. Noun-based systems support only a few actions on the data, and that set does not grow with the number of actors.
In practice I have found that most applications need a mixture of message centric and data centric communication and I have built my actor and mediator implementation to account for this. Organizing messages as either command (message centric) or data (data centric) message types has made my code easier to write, easier to read, reduced coupling between my actors, and lowered the class count in my projects. Each of these aspects increases the simplicity of my codebase and that is how I measure success.
Jon McBee is the Founder and Managing Partner at Composed Systems and is a Certified LabVIEW Architect, Certified LabVIEW Embedded Developer, Certified TestStand Developer, an NI Certified Professional Instructor, a LabVIEW Champion, and a Certified ScrumMaster
I refer you to the following links for more information on this topic: