Up until recently I'd never hit the use case where I needed an application written in one programming language to send messages to an application written in another programming language on the same machine.
Initially, I had two options in mind - sockets or HTTP. Both would have been a lot of work I was hoping to avoid. Fortunately Linux has dbus
which I was vaguely aware of. After some investigation, dbus turned out to be my dream choice.
I'll show you how easy it is to send messages between applications in different languages, by using dbus's RPC capabilities to implement publish/subscribe.
What we want to achieve
Take a look at the diagram below. You can see that we want to create an application called publisher that will allow other applications to register as subscribers and therefore have messages sent to them.
Implementation-wise, the publisher will be written in scala, whilst subscribers will be in ruby and scala (could be any language). Additionally, messages will be sent to the publisher for publishing from the command line.
Structure of dbus services
Before building the applications, it's really important to understand the structure of a dbus service. Luckily, it's really simple as the diagram below illustrates.
Each service has a unique name – this is how other services can find it on the bus. Each service also has a number of paths – analogous to buckets or namespaces which contain methods that other services can call.
So when a service wants to call a method on another service, it:
- Looks up the service by name
- Selects a path
- Selects an interface inside selected path
- Selects a method on selected interface
- Invokes the selected method with correct argument
Creating a publisher
I'm not going to cover setting up projects, or configuring dependencies, I'm just going to show the actual code with explanations. There will be links at the bottom of the page to help get started and you can download this example from github
First thing the publisher does is register itself on the bus with the service name dbus_demo.publisher (line 10). Then it exports – meaning other services can call the methods on it - the "Publish" interface in the path "/" (line 11).
Publisher lives inside the package "dbus_demo" (line 1), so it's full interface name on the bus will be dbus_demo.Publisher.
A really important feature to note occurs on line 57. Here, the publisher is creating a local Handler object which represents an identical object on the subscriber. To be identical the path, interface name, and method name must all be the same in both applications.
Below is the handler. Remeber, this is a trait (interface) because the implementation lives on the service being called - essentially it's used to create a proxy of it.
Creating a scala subscriber
This client will be a completely separate application, with unique code, running inside a different JVM, running inside a different OS process.
Similar to the publisher, the subscriber first registers itself with the bus. Then it needs to register itself with the publisher by calling the publisher's register method. To do this, it again uses a local interface (Publisher) to create a proxy of the remote implementation using getRemoteObject().
Here is the Handler for this project - it has the same package name and method signatures as the interface in the Publisher application. Therefore, it will have the interface name on dbus - bus_demo.client.Handler - which the publisher looks for (publisher code line 57).
Creating a ruby subscriber
the ruby client provides the same functionality as the scala client. It makes a remote call to the Publisher to register as a subscriber and it exports a method called "Handle" on an interface called "dbus_demo.client" in the path "/". Remember, the publisher looks for exactly this when publishing a message to clients (publisher code line 57).
It's nice to work with the ruby library because you can see it makes good use of dynamics instead of requiring mirror classes.
Testing it all out
To show this all working, I'm going to:
Start the publisher (top panel)
Start the scala subscriber (2nd top panel)
Start the ruby subscriber (3rd panel)
Send a message from the command-line to the publisher (bottom panel)
Watch as each terminal window updates – confirming the message reached the publisher and was delivered to all the subscribers. (all panels)
You can get all of the source code used for this example from my github
. Why not extend it and add clients in C, Python, or your favourite language(s)?