Find my posts on IT strategy, enterprise architecture, and digital transformation at ArchitectElevator.com.
A call stack is inherently asymmetric: one method, the "caller", calls another method, the "callee". While the callee is working the caller is stalled. When the callee is done doing the work, control returns back to the caller. The call stack makes sure work continues within the caller exactly where it had left off. The asymmetry between caller and callee is even represented in the UML notation:
Method invocations are depicted as a solid line. Return values are optional and if included are represented as a dashed line. Within the virtual machine or the processor level the asymmetry takes the form of the push/pop and call/ret pairs. Inherently, invoking a method or calling a subroutine is different than returning from a method or subroutine.
In the world of distributed systems, the situation is slightly different. Distributed systems can ultimately only communicate through unidirectional data streams (often represented as "sockets" to the application). If you want to dive deeper down the stack, you could also make the claim that they can only communicate via packets or voltage sequences on some sort of wire. Either way, this means that both invoking a method and returning a result have to be performed in a way that it can be done over a data stream. Therefore, each component participating in remote communications needs to have sender and receiver modules that can turn objects into data streams and vice versa.
The interesting part is that this causes the solution to look a lot more symmetric. For example, in the following picture, which component invokes which one?
You might assume that the picture is drawn from left to right or from top to bottom but any cultural hints aside (they tend to vary across continents anyway) you'll quickly see that the picture is essentially symmetric. Both a caller and a callee have to send a message and receive a message. The only difference is in the order because the caller typically sends a message first and receives a message later, while the callee receives a message first and sends a message afterwards. Otherwise there is little difference between caller and callee.
The fact that one message is the method call (i.e., the "request" message) and the other one is the return (i.e., the "response" message) often manifests itself only in naming conventions or the presence of a <relates-to> element in the reply message that acts as a Correlation Identifier. Some people who equate service-oriented architectures with Web services and Web services with SOAP over HTTP might feel that the request-response model is nicely represented in the HTTP protocol: everything that "goes out" is a request and everything that "comes back" is a response. That works as long as you look it from the perspective of a client , i.e. a consumer of services. For the service provider everything that "comes in" is a request and everything that "goes out" is a response. Sounds pretty symmetric doesn't it? OK, the "client" gets to open and close the connection but once that is done we are back to "data stream out, data steam in". Besides, once you move to a call-back model for long-running services, the "server" now opens a connection to the "client" to deliver the "result" message. Who is now the caller and who is the callee? Sounds confusing? Well, yeah, because this is not the right way to think about these kinds of systems.
The symmetry becomes even more apparent if you zoom in on the parts that convert from the synchronous object-oriented world of the application to the asynchronous data stream world of the network (see figure).
What does it take to get an object on the wire? You typically have to add any system-specific header elements, for example SOAP headers such as WS-Addressing. You then might have to attach any necessary security certificates and finally serialize everything into a data stream. You might also want to encrypt parts of the stream. The receiving end looks pretty much like the mirror image: you first have to deserialize, check any applicable certificates and parse out the header elements to be used by the infrastructure. Whether the data you deal with is a "request" or a "response" is totally secondary. Sending a response message still requires you to attach headers, to serialize, and to deal with security. These functions are typically implemented as a chain of Intercepting Filters, a pattern that is ultimately based on the Pipes-and-Filters principle that forms the basis of most messaging systems.
Therefore, it makes a lot more sense to think about the interaction between the systems as an exchange of messages. The systems are primarily message senders and message receivers. Only a few higher level indicators (such as header fields) indicate which system is the "client" or "caller" and which system is the "server" or "callee". Thinking about the messages first allows for better code reuse and ultimately a simpler system model.
It is nice to see that a number of vendors have come to the same conclusion and are
steering their Web services products into the direction of messaging. Microsoft makes
for a good example. While the original ASP.NET Web Services framework was all about
"how can I call my method over SOAP?", the more recent Web Services Enhancements (WSE) are completely based on a SoapSender
and SoapReceiver
model. Yes, they also include SoapClient
and SoapServer
classes but these are actually inherited from the SoapSender
and SoapReceiver
classes. Both "clients" and "servers" use the same input filter and output filter
chain to provide support for WS-Addressing and WS-Security for inbound and outbound
messages.
If you look a little bit further down the (time)line you'll easily notice that Microsoft's distributed systems strategy has "messaging" written all over it. Indigo is all about messaging.
A call stack mentality is very useful for synchronous, object-oriented applications that live in a single address space. However, when it comes to building distributed systems, the call stack mentality haunts us and makes us not realize the real structure of the system: sending and receiving messages. With message-based asynchrony we achieve symmetry that can save us a lot of duplication and grief.