Communication between Actors is meant to be done by traversing the Actor tree. Assuming we decide not to pass message enqueuers around to all Actors in the tree, we will end up with a lot of message classes and a lot of message routing. The extra messages and routing, however, buys us robustness as Actor E has no way of knowing if Actor C currently exists. But what if we could bypass the tree for certain types of messages in a way that allowed us to decouple our actors from each other...
I'm going to start this post by saying that I am assuming you have some hands on experience with the Actor Framework, if you don't I suggest that you start here. And you really should start, the Actor Framework is a powerful tool that will make it easier for you to write large, scalable applications.
Before diving in to the details, take a look at the video below. It shows the Actor to non-Actor publish-subscribe in action. It's 10 minutes long and starts off a little slow but give it time.
So what did we just see? You may have noticed the mediator pattern popping up again, it made a showing in the last post as well. I'm not going to go into detail on how it works (yet), but I do want to go into detail on how I used it in the video above. This implementation is based on a presentation given by Dmitry Sagatelyan at the 2015 CLA Summit, I strongly encourage you to look through his presentation slides.
The first thing that I do is create a message broker, which is a by reference class that brokers messages between actors in a nicely decoupled way.
Because the message broker is by reference I can branch its wire and safely inject it into Actors (and actors) before they are spun up. Once the message broker has been created I register a data publisher for a data channel named "Test", this method outputs a message transport class.
Notice that I don't need to define a datatype for the data channel. I am just creating the data channel and the message transport that will allow me to put data onto the channel. Once the transport has been created, I can start publishing data on it.
Here is where I define the data type, package the named data into an object, and send it along its way via the message transport. Notice the "Send" method icon shows that this message transport is based on the queue API.
I currently have 5 types of message transport but am tinkering with a few new ideas. This post really only uses the queue message transport and the Actor Framework message transport.
Launching Actors looks like this. The only thing different about this launch from a typical Actor launch is the "Set Message Broker Method".
Note that the "Set Message Broker" method is static dispatch and is a member of the Example Actor class. I mention this because I want to call out that I made a design decision to use composition over inheritance to plug the Actor Framework into the mediator framework. I could have made a parent Actor named Mediator Actor class and put the Message Broker object in its class data and given it a static method named "Set Message Broker" that its children could use, but I didn't.
On to the Actor side of the equation, looking at the project you can see that the Actor is fairly typical. The only mediator-ish method in the class is the "Set Message Broker" method, which we saw above.
Here is the class data, you can see the Message Broker object. The Transport object was added during the demo so that the Actor could publish data as well as subscribe. The Transport object is created in the "Pre Launch Init" method.
There are three things I want to point out here. The first thing to notice is that we are registering the Actor as a subscriber on the Test data channel.
The second thing to notice is that as part of the subscriber registration we are passing in an Actor Framework message transport that holds an Actor Framework Message object.
Take a look at the project again, this is a "Low-Coupled" message, meaning that it only implements the "Do" method, leaving the "Send" method to be implemented by a parent class. If you want a refresher on low-coupled messaging take a look at this.
As a quick aside, I could have registered for this data with the Set Control Value Index message transport and sent the data directly to the gauge controls transfer buffer.
The class data for the Sample Actor Data Msg class is empty. I figured I would show it in case you were curious.
Here is the "Do" method of the Sample Actor Data Msg class. Notice that it is written to expect a message class of type DBL, and if it receives the wrong type it will pass out the expected types default value.
The third thing to notice is that we are registering the Actor as a publisher to the "Test" data stream, and storing the message transport object in the Actors class data. This was added during the demo in order to give the Actor the ability to both publish and subscribe on the same data channel.
The last piece of the puzzle is the "Actor Core" method. It is pretty standard with the exception of the publishing of data that was added during the demo.
As you can guess, there are some features here that bypass some of the built in safety of the Actor Framework. There is also some room for some interesting innovation. I think this is a pretty simple way to extend pub/sub to the Actor Framework, and because the publishers and subscribers are decoupled form each other we are able to do n to m pub/sub with Actors and non-Actors alike. I find this stuff pretty interesting, I'm hoping that you do too.
Jon McBee is a Principal Software Engineer at Cambridge NanoTech and is a Certified LabVIEW Architect, Certified LabVIEW Embedded Developer, Certified TestStand Developer, an NI Certified Professional Instructor, and a LabVIEW Champion