Find my posts on IT strategy, enterprise architecture, and digital transformation at ArchitectElevator.com.
We all know by now that synchronous, object-oriented applications are a thing of the past. If you want to be cool these days you have to be asynchronous. Hey, even Starbucks figured that out! Anyway, our last project had "asynchronous service invocation" already in the list of the requirements so very little convincing was necessary. Being a huge fan of asynchrony and patterns, I quickly started to recite asynchronous invocation patterns. Having learned that quoting other people's books reduces the amount of eye rolling and sighing, I described the patterns in Chapter 8 of Remoting Patterns. This chapter has a nice side-by-side comparison of asynchronous interactions between systems, such as Fire-and-Forget, Callback, Polling.
The great thing about patterns is that they often apply to different contexts and levels of abstraction. As such they allow us to transfer knowledge and experience gained in one area to another area or another level of abstraction. This holds particularly true in areas such as message flows or workflow processing that can span domains as diverse as fluid dynamics, EAI messaging, manufacturing optimization, coffee shops, or composite services.
Interaction patterns enjoy a wide range of applicability as well, particularly in the world of event-driven applications and message-based integration. Unfortunately, this wide applicability can increase the chance of misunderstandings, especially if we deal with remote communication. When I tell a coworker that I use "Asynchronous Callback" for my Web service he or she might think that I use an asynchronous client API. Another colleague who is developing a service provider might think that I want to invoke his service code through threads or delegates. Another person yet might think to use message-oriented middleware between components as supposed to HTTP. These examples portray (at least) three different views that exist when we look at communicating systems:
Let's look at asynchrony from all three viewpoints: (Depending on the nature of the conversation the roles may be more manifold than simply consumer and provider but let's stick with those two for now to exemplify an entity that sends a message and expects a response, and an entity that accepts requests and returns response, respectively.)
It is very common for Web services frameworks to offer asynchronous client libraries.
This functionality is often already baked into the client stub that the toolkit generates
from the provider's WSDL. For example, on the .Net platform the client proxy generates
three methods for every operation listed in the WSDL document. For example, if the
provider exposes an operation GetLoanQuote
, the client proxy exposes the methods:
public LoanQuoteReply GetLoanQuote(LoanQuoteRequest LoanQuoteRequest) {...} public IAsyncResult BeginGetLoanQuote(LoanQuoteRequest LoanQuoteRequest, AsyncCallback callback, object asyncState) {...} public LoanQuoteReply EndGetLoanQuote(IAsyncResult asyncResult) {...}
The BeginGetLoanQuote
and EndGetLoanQuote
methods allow the consumer to invoke the service asynchronously. Or do they? Well,
it depends on your point of view. These methods are wrappers around the BeginInvoke
and EndInvoke
methods in the SoapHttpClientProtocol
base class and provide for an asynchronous (i.e. non-blocking) programming model
for the client application. This type of asynchrony is quite useful to keep the client
application from sitting in a loop waiting for a response from a remote service. In
fact, it is highly recommended to use the BeginXXX and EndXXX methods (and in .Net
2.0 we get a nicer version of these methods based on event handlers). However, this
type of asynchrony has very little to do with the interaction between consumer and
provider. Whether the client application calls GetLoanQuote
or BeginGetLoanQuote
/ EndGetLoanQuote
is completely invisible to the service provider. The client proxy will still open
an HTTP connection, send the request, and keep the connection open until the response
is received. From an observer point of view, this interaction looks rather RPC-like,
i.e. synchronous.
On the provider side things naturally look a bit more synchronous: you need to accept a request and process it. The service can't really buck out before the response is computed. However, many service providers maintain a thread pool so that individual requests can be handled by a pool of worker threads. From the request handler's point of view the invocation of a worker thread is asynchronous -- the handler does not block and wait until the handler completes processing the request. Instead, the handler managers many workers at the same time. This type of processing is commonly implemented in most Service Activators because it provides efficient use of resources and good throughput. But again, this asynchrony is related to the local programming model inside the server process. It has no impact on the client programming model or the conversation protocol between consumer and provider.
If we want to achieve an asynchronous interaction model between client and server (for example to avoid holding connection resources for the entire duration of a long-running interaction) we need to look outside of the consumer and provider programming models. We can, for example, choose a message-oriented communications layer such as JMS or MSMQ. Or we can use two separate HTTP connections -- one for the request message and one for the response message. In either case we can use the familiar asynchronous messaging patterns such as Correlation Identifier and Return Address to manage the relationship between the two messages. Using HTTP is still not asynchronous from the viewpoint of a single message, i.e. the sender of a message still has to wait for the receiver to respond to the request to establish a connection and until the data is transmitted. However, it does provide asynchrony from the viewpoint of the overall conversation. No conversation state (see Enemy of the State) is held in the connection between consumer and provider. Instead, the state travels with the message that is sent by the consumer to the provider and back.
Asynchrony is a broad concept and not surprisingly there are many incarnations. All types of asynchrony have their place but are very different. Therefore, it is important to avoid confusion by specifying the context when we use the asynchronous pattern names.
Interestingly the same viewpoints apply to the notion of transactions. A lot of messaging layers provide client-side transactions or provider-side transactions but no conversation transactions. This is a very interesting topic, but it'll have to wait for another rambling...
Share: Tweet
Follow: Follow @ghohpe SUBSCRIBE TO FEED
More On: DESIGN MESSAGING CONVERSATIONS WEBSERVICES PATTERNS ALL RAMBLINGS