Architecting Distributed Systems with Web Services
Architecting Distributed Systems with Web Services
Although SOAP is typically demoed withsimple RPC-based Web services, such as SkatesTown's inventory check service, the SOAP specification does not mandate any particular communication mechanism or interaction pattern between the participants of a Web-service-enabled distributed system. System designers basically have complete control over the system architecture, choice of communication protocols, message routing, intermediary configuration, and so on. The hard part about having so much flexibility is that without solid experience with distributed systems and good judgment, it is easy to make sub-optimal choices.
The most commonly asked questions about distributed systems based on Web services center around a long-running debate in distributed computing circles regarding the rules and regulations for using RPC and messaging (often identified as Message Oriented Middleware—MOM) to solve problems. Typically, the debate takes the unnecessarily polarized form of "MOM vs. RPC." The fact of the matter is that both messaging and RPC play significant, albeit different, roles in distributed computing. Both approaches continue to be very relevant in the era of Web services.
Unfortunately, a lot of confusion exists about the meaning of the terms, the capabilities of messaging and RPC systems, and the scenarios in which they are best applied. Service-oriented architectures fundamentally can support both models. Therefore, to best take advantage of Web services, it helps to have a good understanding of both. What follows is a brief analysis of the two approaches and their relation to SOAP and Web services. Given that people are generally more familiar with RPCs, we start with a discussion of messaging in its many forms.
As a model for distributed computing, messaging refers to a mechanism for getting systems to interact via the passing of messages. A message is a single unit of communication encapsulating some information. (A SOAP message is a great example.) This is where the differences begin. Messaging models can vary significantly based on the following criteria:
Number of participants and their organization
Synchronicity of message exchanges
Direct versus queued messaging
Quality of service (QoS)
There are three different ways to organize messaging participants (see Figure 3.12). The simplest case is 1-to-1 (point-to-point) messaging, which involves only two systems. An example could be an e-commerce scenario where the client application submits a purchase order to a digital marketplace. In this case, the sender needs to know where to send the message.
Figure 3.12 Messaging patterns.
A slightly more complicated organization is 1-to-many messaging, where the sender sends a single message but copies of it go to multiple recipients. This is often referred to publish/subscribe or topic-based messaging. The idea is that the sender is a publisher that sends a message to a "topic" and that the recipients are all the systems that have subscribed to receive notifications on this topic. E-mail distribution lists are a good example of this type of messaging. The name of the distribution list is the topic, and the subscribers are all the e-mail addresses on the list.
Finally, many-to-many messaging involves a pattern of message exchange among any number of participants. Clearly, in this case, some system in the middle (typically some type of a workflow engine supporting business processes) needs to direct message traffic. This is described by the cloud in Figure 3.12.
There are four common messaging interaction patterns (see Figure 3.13). One-way (fire-and-forget) messaging involves the simple sending of a message from one system to another. No response is generated at the application level. Of course, depending on the transport (such as HTTP), a response might be generated at the network level. In the case of request-response messaging, a response message is generated for every request message. The response message is sent from the target of the request message to its source. Chapter 6 describes how requests and responses can be correlated and how multiple request-response pairs can be organized into logical "conversations."
Figure 3.13 Interaction patterns.
The other two interaction patterns, notification and notification-response, are mirror images of one-way and request-response. They are callback patterns. Rather than a client system pushing messages to a server system, the server system is pushing messages to the client. The stock ticker application you might have on your desktop is a perfect example of notification combined with publish-subscribe messaging. Chapter 6 gets into more detail about Web service interaction patterns.
Messaging can be either synchronous or asynchronous. In synchronous messaging, a send operation does not complete until the target of the message has finished processing the message. Asynchronous messaging is harder to define. Typically, the send operation will return immediately (or very quickly), before the target has processed the message. Response messages, if any, typically arrive via callbacks.
Direct vs. Queued Messaging
The synchronicity of messaging is controlled by the presence of messaging middleware, particularly queuing systems. Direct messaging works without any middleware present. For messages to be exchanged, a direct connection between the source and the target(s) must be available. This is why it is sometimes referred to as connection-oriented messaging. You can get some amount of asynchronicity in direct messaging by using threads to manage the sending and receiving of messages.
Indirect messaging involves some type of message queuing. Queues provide message buffering and dispatch capabilities. Consider the e-mail server example from earlier in the chapter. An e-mail server is a perfect example of a message queuing system. When you send an e-mail message, your e-mail client does not contact the e-mail client of the person you are trying to reach. Instead, your e-mail client sends the message to a local e-mail server. The server saves the message in some safe place and waits for a good moment to send it out. Typically, many messages are sent at once. This is the buffering function. The dispatch function has to do with the e-mail server inspecting the target e-mail addresses and deciding where to forward the e-mail message. In some cases, an e-mail message will make several hops between e-mail servers before it arrives at the destination e-mail server where your mail client can read it. This configuration is so powerful because it works even in the cases where mail clients and even some mail servers are offline for long periods of time. A mail server will keep trying to send e-mail for several days and will store received messages potentially indefinitely.
Figure 3.14 contrasts direct messaging (the topmost configuration) with a number of possible queuing configurations. In the second and third configurations, the queuing system acts primarily as a message buffer. For example, if the receiver is not on the network, the message will still be safely stored in the queue. The last configuration is the most interesting, in that the message can be moved from the local to the remote system without either the sender or the receiver being online—the message queuing systems can do the job by themselves. In addition, the presence of more than one queuing system allows for flexible message dispatch.
Figure 3.14 Variations of queuing configurations.
Quality of Service
Another important aspect of messaging is quality of service (QoS). Direct messaging exhibits the QoS parameters with which we are most familiar, such as security and transaction management. When queuing is in use, other types of QoS become available. For example, messages can be stored in the queuing server in various ways: in memory (the fastest queuing mechanism but one that does not guarantee against system failure) or in some persistent store, such as a DBMS.
Further, transactions can guarantee that the message is sent to the receiver once and only once or not at all. In the case of message delivery failure, QoS policy might dictate that a failure notification is sent to the message sender. In addition, it is common QoS policy to send acknowledgement notifications that the message has been successfully delivered to the receiver. These types of QoS considerations are very relevant to Web services. Chapter 5 looks in more detail at some QoS aspects.
The last but not least important aspect of messaging is the format of message data. Most messaging systems allow the transfer of text and binary data, to enable the easy transfer of XML. Some newer messaging systems treat XML messages specially and try to use an optimized XML encoding format. There is also the notion of queues that can automatically allow only XML messages that comply with certain schema. Some platform-focused messaging systems, such as Java Messaging Service (JMS) middleware and Microsoft's .NET messaging server, also allow for the automatic serialization of application data (Java objects in the case of JMS and Common Language Runtime [CLR] data structures in the case of Microsoft).
Messaging Versus RPC
If messaging is all about possibilities and variations, RPCs are much more constrained. As the name suggests, the goal of RPCs is to make the invocation of remote code seem like a local procedure call (LPC). To make an RPC call, you need the following information:
A target to invoke
An operation name
Optionally, parameters to pass to the operation
Therefore, whereas messaging is primarily about data (which can be in any conceivable format), RPCs are about combining specific application-level data with remote code. This is the one fundamental difference between messaging and RPC. A nice side-effect is that programmers using RPC do not have to worry about manually performing data encoding and decoding—something that typically has to happen when using messaging systems, especially across programming languages and platforms.
Another way to state the main difference between messaging and RPC is to note that messaging deals with generic APIs such as sendMessage(), getMessage(), and registerMessageResponseCallback(), whereas RPCs deal with special-purpose APIs that vary based on the interface of the target that is being invoked. For example, if you are trying to invoke a remote EJB that has a processOrder() method, you will most likely call the processOrder() method of a local object that acts like a proxy to the remote EJB. Chapter 6 discusses this topic in much more detail.
Another key difference between RPCs and messaging is that RPCs are direct invocations. There is no queuing mechanism; the backend must be running and it must be directly accessible at a well-known location. This limits the dispatch capabilities of RPC middleware. MOM message dispatch can be much more flexible.
Finally, extensive use of RPCs tends to result in somewhat brittle distributed systems. Because the APIs are fine-grained, even small changes in the data being passed around can break the system. Messaging uses much rougher-grain data exchanges and is therefore more likely to sustain small changes in the data being exchanged without failure.
Apart from these key differences, RPCs and messaging have many similarities:
RPCs can be implemented on top of a request-response messaging pattern.
Contrary to popular belief, however, RPCs do not have to have a request-response messaging pattern. Some systems support one-way RPCs.
In addition, RPCs do not have to be synchronous. Some systems automatically spawn threads to wait in the background for RPC responses.
RPCs and messaging share many of the same QoS requirements such as security and transaction management.
Direct, synchronous, 1-to-1 messaging can be simulated via a simple RPC, e.g., void sendMessage(data).
It should become clear by now that the real issue isn't which of the two approaches to distributed computing is better (the simple interpretation of "messaging vs. RPC") but when each approach should be used in the world of Web services. To answer this question, after we have mentioned so many possible variations of both messaging and RPC, it helps to establish some stereotypes. When working with Web services, it will generally be the case that:
RPCs will be direct, synchronous, request-response invocations that pass encoded application-level data structures from a client to a target backend that implements the RPC functionality.
Messages will carry XML data. The interaction pattern is most likely to be one-way or request-response. Simple architectures will use direct messaging. The organization of participants will likely be 1-to-1. More advanced architectures will be queued and therefore asynchronous.
In both cases, messages will be represented on the wire using SOAP. QoS-related information that is part of the message will be represented as message headers. A good example would be an authentication header that carries a username and password; Chapter 5 shows an example.
Table 3.2 presents a number of benefits and concerns about using messaging and RPC. Based on these and the current state-of-the-art in Web service middleware and tooling, we would recommend that you go with a simple RPC-based solution or a direct messaging solution unless disconnected operation will be of benefit, the system requires 1-to-many interactions, or synchronous operation is causing performance problems.
Table 3.2 Pros and Cons of Messaging and RPC for Web Services
The basic messaging APIs are very simple.
Any data can be passed.
Separates data from the code that operates on it.
Applications must perform manual data encoding/decoding.
Same as above, plus...
Asynchronicity spreads the load and improves performance.
Allows for disconnected operation.
Allows for 1-to-many and many-to-many interactions.
Same as above plus...
Most useful forms of messaging require a queuing infrastructure.
Current message queuing products do not interoperate well.
Asynchronicity makes programming more difficult.
Local APIs match backend APIs.
Synchronicity makes programming easy.
Application data is automatically encoded/decoded.
Exceptions provide a good error-handling mechanism.
RPC products interoperate reasonably well.
Synchronicity can cause bottlenecks.
Backend must be running for RPCs to succeed.
Only 1-to-1 interactions are supported.
We would expect that as messaging middleware vendors embrace Web services to a greater extent and as more Web services become increasingly used in the context of complex business process workflows, the importance of Web service messaging will grow. Broad standardization efforts such as ebXML (http://www.ebxml.org) and Java API for XML Messaging (JAXM, http://java.sun.com/xml/jaxm/index.html) will help speed up the process.
So far in this chapter we have presented several examples of SOAP-based RPC without ever mentioning the details of representing RPCs in SOAP messages as described by the SOAP specification. The rules are very simple.
Recall that to invoke an RPC, you need a target URI, an operation name, some parameters, and any amount of context information (such as security context). Any such context information is modeled as SOAP headers.
SOAP's RPC binding does not specify how the target URI is going to be provided. In other words, it leaves it up to the SOAP processor to determine how to dispatch a SOAP RPC request to a target backend. There are three common ways to do this dispatch. Two of these are HTTP-specific, and the other is based on the contents of the SOAP message:
In the case of HTTP, the SOAP processor can dispatch based on the target URI (as in the inventory check example).
Alternatively, it may dispatch based on the value of the SOAPAction HTTP header that comes as part of the HTTP request.
Alternatively, it can use the value of the namespace URI for the first element inside the SOAP body.
Most Web services engines do not support all these dispatch mechanisms. Axis can be configured to work with any combination.
In the language of the SOAP encoding, the actual RPC invocation is modeled as a struct. The name of the struct (that is, the name of the first element inside the SOAP body) is identical to the name of the method/procedure. This is not a problem, because the character set of XML elements is a superset of the character set of valid identifier names in programming languages. Every in and in-out parameter of the RPC is modeled as an accessor with a name identical to the name of the RPC parameter and type identical to the type of the RPC parameter mapped to XML according to the rules of the active encoding style. The accessors appear in the same order, as do the parameters in the operation signature.
The RPC response is also modeled as a struct. By convention, the name of the struct is the same as the name of the operation, with Response appended to it. There are accessors for the operation result and all in-out and out parameters. The result is the first accessor, followed by the parameters in the order they appear in the operation signature. By convention, the result element's name is the same as the name of the operation, with Result appended to it.
Java developers are not used to the concept of in-out or out parameters because, typically, in Java all objects are automatically passed by reference. When using RMI, simple objects can be passed by value, but other objects are still passed by reference. In this sense, any mutable objects (ones whose state can be modified) are automatically treated as in-out parameters.
In Web services, the situation is different. All parameters are passed by value. SOAP has no notion of passing values by reference. This design decision was made in order to keep SOAP and its data encoding simple. Passing values by reference in a distributed system requires distributed garbage collection. This not only complicates the design of the system but also imposes restrictions on some possible system architectures and interaction patterns. For example, how can you do distributed garbage collection in a queued messaging architecture when the requestor and the provider of a service can both be offline at the same time?
Therefore, for Web services, the notion of in-out and out parameters does not involve passing objects by reference and letting the target backend modify their state. Instead, copies of the data are exchanged. It is then up to the service client code to create the perception that the actual state of the object that has been passed in to the client method has been modified. Different Web service clients might have different ways to do this.
Consider the following operation signature:
boolean doCheck(in String sku, in int quantity, out int numInStock)
Some possible SOAP RPC request and response bodies are:
<!-- RPC request body --> <SOAP-ENV:Body> <doCheck> <sku xsi:type="xsd:string">947-TI</sku> <quantity xsi:type="xsd:int">1</quantity> </doCheck> </SOAP-ENV:Body> <!-- RPC response body --> <SOAP-ENV:Body> <doCheckResponse> <doCheckResult xsi:type="xsd:boolean">true</doCheckResult> <numInStock xsi:type="xsd:int">150</numInStock> </doCheckResponse> </SOAP-ENV:Body>
Of course, if a description of the operation is available, you can generate a schema for all the elements in the SOAP body. Doing so would eliminate the need to use xsi:type everywhere in the SOAP message. Chapter 6 looks in more detail at the mechanisms for doing this.
The technical term for non-RPC SOAP messaging is document-centric messaging. The name comes from the fact that the data sent over SOAP is represented as an XML document embedded inside the SOAP envelope. Although the RPC binding for SOAP has a number of rules governing the representation and encoding of operation names and parameters, simple SOAP messages have absolutely no restrictions as to the information that can be stored in their bodies. In short, any XML can be included in the SOAP message. The next section of this chapter shows an example of SOAP-based messaging.