There is a divide in today's computing world between the small scale and the large scale. The technologies of the internet and the desktop are different. Perhaps the problem domains themselves are different, but I don't think so. I think that the desktop has failed to learned the lessons of the web. SOAP is an example of that desktop mindset trying to overcome and overtake the web. One example of the desktop mindset overcoming and overtaking the open source desktop is the emerging technology choice of DBUS.
DBUS is an IPC technology. Its function is to allow procesess on the same machine to communicate. It's approach is to expose object-oriented interfaces through an centralised daemon process that performs message routing. The approach is modelled after some preexisting IPC mechanisms, and like those it is is modelled after gets things wrong on several fronts:
- DBUS does not separate its document format from its protocol
- DBUS pushes the object-oriented model into the interprocess-compatiblity space
- DBUS does not have a mapping onto the url space
- DBUS does not separate name resolution from routing
From a RESTful perspective, DBUS is a potential disaster. I know it was (initially at least) targetted at a pretty small problem domain shared by kde and gnome applications, but the reason I feel strongly about this is that I have gone down this road myself before. I'm concerned that dbus will come to be considered a kind of standard interprocess communications system, and that it will lock open source into an inappropriate technology choice for the next five or ten years. I'll get to my experiences further down the page. In the mean-time, let's take those criticisms on one by one. To someone from the object-oriented world the approach appears to be pretty near optimal, so why does a REST practictioner see things so differently?
Decoupling of Document Format from Protocol
Protocols come and go. Documents come and go. When you tie the two together, you harm the lifecycle of both. DBUS defines a document format around method calls to remote objects. There have been protocols in the past that handled this, and there will be in the future. There are probably reasons that DBUS chose to produce its own protocol for function parameter transmission. Maybe they were even good ones. The important thing for the long-term durability of DBUS is that there should be some consideration for furture formats and how they should be communicated.
Objects for interprocess communication
The objects in an object-oriented program work for the same reason that the tables within SQL database work: They make up a consistent whole. They do so at a single point it time, and with a single requirements baseline. When requirements change, the object system changes in step. New classes are created. Old classes retooled or retired. The meaning of a particular type with the object system is unambiguous. It neither has to be forwards or backwards compatible. It must simply be compatible with the rest of the objects in the program.
Cracks start to emerge when objects are used to build a program from parts. Previous technologies such as windows DLLs and COM demonstrate that it is hard to use the object and type abstraction for compatability. A new version of a COM object can add have new capabilities, but must still support the operations that the previous version supported. It indicates this compatability by actually defining two types. The old type remains for backwards-compatibility, and a new one inherits from the old. A sequence of functionality advances results in a sequence of types, each inheriting from the previous one.
This in and of itself is not a bad thing. Different object systems are likely to intersect with only one of the interface versions at a time. The problem is perhaps deeper within object-orientation itself. Objects are fundamentally an abstraction away from data structures. Instead of dealing with complex data structures everywhere in your program, you define all of the things you would like to do with the data structure and call it a type. The type and data structure can vary independently, thus decoupling different parts of the application from each other. The trouble is that when we talk about object-oriented type, we must conceive of every possible use of our data. We must anticipate all of the things people may want to do with it.
Within a single program we can anticipate it. Between programs with the same requirements baseline, we can anticipate it. Between different programs from different organisations and conflicting or divergent interests, the ability to anticipate all possible uses becomes a god-like requirement. Instead, we must provide data that is retoolable. Object-orientation is built around the wrong design principle for this environment. The proven model of today is that of the web server and the web browser. Data should be transmitted in a structure that is canonical and easy to work with. If it is parsable, then it can be parsed into the data structures of the remote side of your socket connection and reused for any appropriate purpose.
Mapping to URL
DBus addresses are made up of many moving parts. A client has to individually supply a bus name (which it seems can usually be ommitted), a path to an object, a valid type name that the object implements, and a method to call. These end up coded as indivdual strings passed individually to dbus methods by client code. The actual decomposition of these items is really a matter for the server. The client should be able to unambiguously be able to refer to a single string to get to a single object. The web does this nicely. You still supply a separate method, but identifying a resource is simple. https://soundadvice.id.au/ refers to the root object at my web server. All resources have the same type, so you know that you can issue a GET to this object. The worst thing that can happen is that my resource tells you it doesn't know what you are talking about: That it doesn't support the GET method.
Let's take an example DBUS address: (org.freedesktop.TextEditor, /org/freedesktop/TextEditor, org.freedesktop.TextEditor). We could represent that as a url something like <dbus://TextEditor.freedesktop.org /org/freedesktop/TextEditor;org.freedesktop.TextEditor> It's a mouthful, but it is a single mouthful that can be read from configuration and be passed through verbatim to the dbus system. If you only dealt with the org.freekdesktop.TextEditor interface, you might be able to shorten that to <dbus://TextEditor.freedesktop.org /org/freedesktop/TextEditor>.
There are still a couple of issues with that url structure. The first is the redudancy in the path segment. That is obviously a design decision to allow different libraries within an application to regieter paths independently. A more appropriate mechanism might have been to pass base paths into those libraries for use when registering, but that is really neither here nor there. The other problem is with that authority.
Earlier versions of the uri specification allowed for any naming authority to be used in the authority segement of the url. These days we hold to DNS being something of the one true namespace. As such, we obviously don't want to go to the TextEditor at freedesktop.org. It is more likely that we want to visit something attached to localhost, and something we own. One way to write it that permits free identification of remote dbus destinations might be: <dbus://TextEditor.freedesktop.org.benc.localhost /org/freedesktop.TextEditor>. That url identifies that it is a local process of some kind, and one within my own personal domain. What is still missing here is a naming resolution mechanism to work with. We could route things through dbus, but an alternative would be to make direct connections. For that we would need to be able to resolve an IP address and port from the authority, and that leads into the routing vs name resolution issue.
Routing vs Name Resolution
The easist way to control name resolution is to route messages. Clients send messages to you, and you deliver them as appropriate. This only works, of course, if you speak all of the protocols your clients wants to speak. What if a client wanted to replace dbus with the commodity interface of http? If we decoupled name resolution and routing, clients that know how to resolve the name can speak any appropriate protocol to that name. The dbus resolution system could be reused, even though we had stopped using the dbus protocol.
Consider an implementation of getaddrinfo(3) that resolved dbus names to a list of ip address and port number pairs. There would be no need to route messages through the dbus daemon. Broadcasts could be explicitly transmitted to a broadcast service, and would need no special identification. They could simply be a standard call which is repeated to a set of registered listeners.
Separating name resolution from routing would permit the name resolution system to survive beyond the lifetime of any individual protocol or document type. Consider DNS. It has seen many protocols come and go. We have started to settle on a few we really like, but DNS has kept us going through the whole set.
Coupling and Decoupling
There are some things that should be coupled, and some not. In software we often find the balance only after a great deal of trial and error. The current state of the web indicates that name resolution, document transfer protocol, and document types should all be decoupled from each other. They should be loosely tied back to each other through a uniform resource locator. The web is a proven sucessful model, however experimental technologies like CORBA, DBUS, and SOAP have not yet settled on the same system. They couple name resolution to protocol to document type, and then throw on an inappropriate object-oritented type model to ensure compatability is not maintainable in the long term and on the large scale. It's not stupid. I made the same mistakes when I was fresh out of university at the height of the object-oriented frenzies of the late 90's.
I developed a system of tightly-coupled name resolution, protocol, and document type for my employer. It was tailored to my industry, so had and continues to have important properties that allow clients to achieve their service obligations across all kinds of process, host, and network failures. What I thought was important in such a system back then was the ability to define new interfaces (new O-O types) easily within that environment.
As the number of interfaces grew, I found myself implementing adaptor after adaptor back into a single interfacing system for the use of our HMI. It had a simpler philosophy. You provide a path. That path selects some data. The data gets put onto the HMI where the path was configured, and it keeps updating as new changes come in.
What I discovered over the years, and I suppose always knew, is that the simple system of paths and universal identifiers was what produced the most value across applications. The more that could be put onto the interfaces that system could access, the easier everything was to maintain and to "mash up". What I started out thinking of as a legacy component of our system written in outdated C code turned out to be a valuable architecural pointer to what had made previous revisions of the system work over several decades of significant back-end change.
It turns out that what you really want to do most of the time is this:
- Identify a piece of data
- GET or SUBSCRIBE to that data
- Put it onto the screen, or otherwise process it
The identity of different pieces of data is part of your configuration. You don't want to change code every time you identify a new piece or a new type of data. You don't want to define too many ways of getting at or subscribing to data. You may still want to provide a number of ways to operate on data and you can do that with various methods and document types to communicate different intents for transforming server-side state.
What the web has shown and continues to show is that the number one thing you want to do in any complex system is get at data and keep getting at changes to that data. You only occasionally want to change the state of some other distributed object, and when you do you are prepared to pay a slightly higher cost to achieve that transformation.
Benjamin