Level: Introductory Bobby Woolf, ISSW WebSphere J2EE Consultant, IBM James Snell (jasnell@us.ibm.com), IBM Software Standards Development, IBM
12 Aug 2005 Dispel the myths of an Enterprise Service Bus and learn how you can apply this architectural style to the implementation of Service-Oriented Architecture-based applications.
Introduction
With the growing emphasis on Service-Oriented Architecture (SOA) and the growing complexity in the family of Web services specifications, there is understandably confusion about such big-sounding terms as Enterprise Service Bus (ESB). Is it a product? Is it just a marketing term? Does it represent something tangible that you can actually use? How does it fit in with all of the other Web services and SOA concepts that have been passed around? Here we will attempt to clear up at least some of this confusion.
This paper shows the role an ESB plays in an application integration and Service-Oriented Architecture. It does not focus on specific features, APIs, or products, but rather on the responsibilities of an integration architecture that can be encapsulated in and handled by an ESB. It shows what an ESB does for architects.
Service-oriented architectural patterns
While Enterprise Service Bus (ESB) may seem like a vacuous marketing term designed only to get you to buy more software, the term actually has meaning for something useful. It refers to an architectural style that you can apply to the implementation of SOA-based applications, a set of conduits for integrating service consumers with service providers. It can also refer to a product which implements that style. To understand the style better, it is important to provide a high-level survey of the various ways you can create service-based systems.
Web services
When someone says "Web services," folks typically think of object-oriented-style interfaces whose methods are invoked using XML-encoded SOAP messages passed back and forth synchronously using HTTP POST operations. Some object on a server provides some collection of methods, each of which has a set of input parameters and an output value. A client creates a SOAP message that encodes the input parameters and posts that SOAP message to an HTTP endpoint somewhere; then some piece of server code decodes the input parameters, invokes some method on the underlying object, and encodes the method's return value into a second SOAP message that is returned in the body of the HTTP response message. This Remote Procedure Call (RPC) process, illustrated in Figure 1, is known generally as a Remote Procedure Call-style (RPC-style) Web service.
Figure 1: A simple RPC-style Web service
The RPC-style Web service pattern is characterized by several points:
-
All operations are synchronous. Just like when you invoke an operation against an object-oriented method or procedural function, operations invoked using the RPC-style Web services architectural pattern block while the request is being processed and the response is generated. The thread of execution only resumes once a response has been received.
-
All operations are generally backed by a singleton object instance. RPC-style Web services generally follow the same basic implementation pattern. Some object defines a number of methods. When the so-called SOAP engine receives the request, it breaks it down and identifies the object whose method is to be called. An instance of the object is created on the fly and destroyed following the completion of the operation, or a singleton instance is used (and reused for each request).
-
SOAP messages encode parameters and return values. The SOAP messages that are passed back and forth are XML-encoded representations of a collection of arguments and return values. The structure of the message is generally tightly bound to the signature of the method being invoked by the underlying object implementation.
Considering these characteristics, RPC-style Web services are generally best handled as if they were any other type of object available for a developer to use. The only difference is that there is a platform-independent communications layer inserted between the object and its client.
Service interfaces
In traditional Object-Oriented (OO) design, it is common to have multiple object instances that implement the same interface. Each implementation class is essentially a different provider that offers distinct functionality relative to the other implementations of the interface. For example, Listing 1 shows four classes that all implement the Math interface.
Listing 1: Object-oriented interfaces
interface Math {
double invoke(double x, double y);
}
class Add implements Math {
double invoke(double x, double y) {
return x + y;
}
}
class Subtract implements Math {
double invoke(double x, double y) {
return x - y;
}
}
class Multiply implements Math {
double invoke(double x, double y) {
return x * y;
}
}
class Divide implements Math {
double invoke(double x, double y) {
return x / y;
}
} |
In our client code, wherever we want to utilize the Math interface, we would need to decide which specific implementation instance we wish to bind to. The choice would be determined, in large part, by the specific goal we're trying to achieve: If we wish to perform an addition operation, we would bind to an instance of the Add class; if we wish to perform a subtraction operation, we would bind to an instance of the Subtract class; and so forth. Figure 2 shows a client invoking the same Math interface multiple times; each invocation is bound to a different implementation.
Figure 2: Typical object sequence diagram with multiple interface implementation classes
Now imagine that we are dealing with fairly typical RPC-style Web services that implement an operation for checking the current price for a given stock symbol. Suppose that we have a single WSDL-defined port type that is implemented by three different service providers -- say stock traders Schwab, Fidelity, and E*TRADE. Ignoring the fact that the method invocation occurs by sending SOAP messages back and forth across a HTTP connection, the interaction between client and service provider is essentially identical to what you see in Figure 3.
Figure 3: Typical RPC-style Web service invocations with multiple service providers
Discovery and integration
What should also be clear is that RPC-style Web services suffer from the exact same kinds of discovery and integration problems suffered by the traditional object method invocation pattern, namely the following:
- How does a client become aware of multiple implementation classes of a given common interface?
- How can multiple implementation classes be selected dynamically based on a variety of run-time selection criteria?
- How can the operation request and response be decoupled so the thread of execution does not have to block until a response is received?
- How can cases be handled that do not fit neatly into to the synchronous request-response pattern typically exposed by the object-oriented method pattern?
Anyone familiar with OO design principles knows that these questions have been answered long ago through the creation and application of design patterns. For example, as an alternative to calling some GetQuote method on an object instance, the client code might post a GetQuote message to an input queue. Some back-end daemon process listening for messages posted to the input queue would receive the message and dispatch it to some piece of code capable of handling the message. The handling code would process the request and prepare some response message that is posted to an output queue. The client code would either poll the output queue for the response or wait for some kind of call-back notification when the response message has been delivered.
Figure 4 shows a client-invoking method that puts a request on an input queue and gets the response from an output queue. Meanwhile, a dispatcher detects the request and invokes the implementation, which runs the method and puts the result on the output queue.
Figure 4: A typical message queue sequence diagram
This form of asynchronous, message-oriented programming model has been around for quite some time and is, of course, broadly implemented in enterprise application development. This is due simply to the fact that it is much more scalable and flexible for large-scale applications than direct object method invocations.
The point of all this: Just like in object-oriented development, where there are many different architectural patterns that developers can draw from when implementing their applications, SOA offers many different ways of implementing service-oriented applications that do not follow the synchronous request-response pattern seen in the RPC-style Web services approach. The ESB is one of those alternative implementation patterns.
The ESB architectural pattern
An ESB is a mediator between clients that invoke services and providers of those services, simplifying both clients and providers by handling the responsibilities of connecting them. In his blog, Robert Sutor, IBM vice president of Standards and Open Source, has described eight defining principles an ESB must embody:
-
Universality -- Provides a connectivity layer spanning extended enterprise environments
-
Heterogeneity -- Provides multiplatform, multiprotocol, and multi-API message-oriented support layer capable of tying together heterogeneous systems
-
Interoperability -- Provides support for open protocols to enable interoperability between systems from multiple vendors
-
Incremental integration -- Provides the ability to grow a system dynamically as needs expand
-
Quality of service -- Provides various qualities-of-service, such as security, performance, reliability, scalability, and so on
-
Substitution -- Utilizes open APIs to ensure the ability to substitute vendor implementations
-
Event orientation -- Provides the ability to decouple applications that generate business events from applications that subscribe and respond to those events
-
Service orientation -- Provides the ability to decouple applications by abstracting key functions as services following the SOA design methodology
On a technical level, these eight points are essentially saying that rather than having a client talk directly to a service provider, requests are routed through a message- and event-oriented middleware system that handles all of the specific details of locating the service provider, negotiating integration with the service provider, interacting with the service provider, and so on.
Figure 5 shows an ESB mediating between a client invoking a service and a provider of the service.
Figure 5: 50,000-foot view of an Enterprise Service Bus sequence diagram
But what do we mean by "handling all the specific details?" The answer is best illustrated by considering a similar concept that will be familiar to many enterprise Java developers.
Selecting an implementation of an interface
Selecting a service provider to invoke is rather similar to selecting which implementation of an interface to use. Go back to the Math interface example illustrated earlier. There, one interface is implemented by four different classes. As a Java developer using one or more implementations of the Math interface, we first have to know what implementations of that interface are available. The options are as follows:
- When we're writing the code, some piece of documentation (for example, javadocs) provides a list of implementation classes. I select one and create an instance of the object for my code to use, which looks like this:
Math add = new Add();
- We write our code in such a way that the specific implementation of the Math interface is selected dynamically at run-time based on some selection criteria, as shown here:
Math math = Math.getInstance("add");
- We write our code in such a way that some other piece of code is responsible for selecting which implementation of the Math interface our code will use:
Math math = Math.getInstance();
- We write our code in such a way that we invoke some abstract method, and some underlying, hidden logic is responsible for dispatching the request to an appropriate implementation, as follows:
Math.invoke("add", x, y);
Java developers will likely use a combination of these approaches in any relatively complicated application. For example, as shown in Listing 2, client code using the Java XML APIs (javax.xml.*) never has to specify which XML parser implementation to use. That decision is implemented using the third approach -- the decision is configured inside the factory and hidden from the client.
Listing 2: Using the Java XML APIs
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.newDocument(); |
In the approach taken by the XML APIs, a developer doesn't know exactly which XML parser is available for the JVM to use, thereby reducing the overall complexity of the code and making it more portable and more manageable. With Option 1 above, it is the developer's responsibility to discover and bind to an appropriate implementation of the desired interface. With options 2, 3 and 4, an underlying, hidden piece of code locates an appropriate implementation instance of the desired interface.
Service discovery in the ESB
Now let's switch back to the realm of service-oriented applications and Web services. It has always been important for a service consumer to discover a service provider. The idea is that some third-party registry maintains a database of known service providers. The client queries that registry either dynamically at run-time or, more likely, at development time to locate one or more of those providers that meet some defined criteria. For example, a client may want to locate all service providers that implement the GetQuote port type. The traditional approach for accomplishing this in Web services has been to submit a query to a UDDI server, parse the results, select a provider, and invoke the service, as shown in Figure 6.
Figure 6: A typical UDDI discovery interaction
When performed at development time, this process is very similar to the first option listed above in which we explicitly selected the Add class' implementation of the Math interface. What we end up with is code similar to that in Listing 3.
Listing 3: Specifying a particular provider
Service service =
new Service(
"http://example.schwab.com/services/stocks");
Call call = service.createCall(...);
...
call.invoke(...);
|
In this case, our client code might not care what development platform or operating system hosts Schwab's stock quote service, but it is still tightly bound to a specific implementation of the GetQuote port type and its provider. If Schwab so much as changes the URL of its GetQuote service (equivalent to changing the Java package of the Add implementation class), every client that is thusly-bound to the service will be affected and will have to be updated. The only way to ensure that the client is using the current binding information for the service would be to requery the UDDI registry before every individual invocation of the GetQuote operation. This causes the client to waste a lot of effort making repeated UDDI lookups.
One approach to simplifying this problem is to introduce a broker to act as an intermediary for invoking the Web service. The client no longer invokes the service provider directly, but rather invokes a service proxy in the broker, which in turn invokes the service provider, as shown in Figure 7. This is equivalent to the fourth option listed above, in which some static method is invoked on an intermediary object. The job of that intermediary object is to locate an appropriate implementation so that our code does not have to perform the discovery operation itself. (The client might or might not have to perform some form of discovery operation to locate the service broker. The broker's URI should be stable; once it has been discovered, the client can generally use it reliably across multiple service invocations.)
Figure 7: Brokered discovery of service providers
The fundamental idea here is that use of the ESB pattern shifts the burden of selecting an appropriate service provider from the client code to some piece of middleware, resulting in an architecture that is fundamentally less complicated for the service consumer.
The service broker not only relieves the client of the burden of locating the service provider, it also enables a flexible approach for introducing efficiencies in the service invocation process, flexibility that would otherwise be difficult for the clients to manage on their own. For example, the service broker might be able to cache the UDDI data to improve the performance of the service provider selection process. Or perhaps the service broker would encapsulate business logic that varies the selection of service providers based on established service level agreements or business rules.
This is the advantage of the Enterprise Service Bus model: The ability to manage the service invocation process while simplifying the consumers and not burdening the providers.
So let's look back at the defining principles of ESBs listed earlier. This model of an encapsulated service broker embodies many of those ESB principles (qualities of service, substitution of service providers and vendor implementations, service orientation, universality, heterogeneity) as being natural outgrowths of the encapsulated broker model. That is, the ESB allows for the loosely-coupled integration of enterprise application components by encapsulating specific details of the interaction between those components into a middleware broker.
Asynchronous messaging in the ESB
Another significant aspect of the ESB architectural pattern is its emphasis on message- and event-oriented communication between components. RPC-style Web services use the same synchronous request-response pattern that is typically seen in OO-style object method invocations. The ESB, however, encorages the use of the asynchronous message bus style interaction pattern illustrated in Figure 4 earlier. Figure 8 shows the message queue approach applied to service invocation, specifically our stock quote service.
Figure 8: Asynchronous message queue based interaction between the client and the middleware broker
With this approach, all interactions between the client and the intermediate middleware occur asynchronously using a collection of input and output message queues. When a response is available, the middleware either triggers some form of event callback, notifying the client of the response, or relies on the client to periodically poll the output queue for the response. The interaction between the middleware and the service provider might or might not be handled asynchronously.
The client never really knows what provider handles its service requests. All the client does is drop requests into a message queue and receive responses (if any are coming) from an output queue. The operation of the service broker, the selection of an underlying service provider, and any other processes that the intermediary wishes to perform are completely transparent to the client.
Service providers can connect to the message bus in exactly the same way -- listening to request queues and sending results on response queues -- such that they might never know about the other available providers of the same service or even the identity of the client that initiates a request. The provider is simply handed a request to process.
So RPC-style Web services resemble OO method invocation: The client synchronously invokes methods against objects using SOAP envelopes that encode input parameters and return values. With an ESB, the client can send request messages and receive response messages asynchronously using message queues in a fashion more reminiscent of the Java Messaging Service (JMS) than an OO method invocation.
Additional characteristics of the ESB pattern
As we discussed, two of the most significant aspects of the ESB architectural style are that it encapsulates business logic into middleware and enables more flexible message- and event-oriented interaction patterns. There are several other characteristics worth noting.
One of the shortcomings of traditional message-oriented middleware solutions (for example, Java Message Service (JMS)) is that they are rarely self-describing. From an application's perspective, there could be many different queues that receive or deliver messages. But which is the correct channel to invoke a specific service? What format must the message have? The caller can't just put any request message format on any request channel; it has to know exactly the right message format and channel for the specific service it wishes to invoke. Otherwise, it might end up buying an airline ticket when what the end-user really wanted was a book. Furthermore, once the application knows the request channel and message format to use, it needs to know the correct response channel to listen to and what response format to expect.
The ESB pattern is based on fundamental SOA design principles. As a result, the ability for channels to self-describe becomes inherent through the use of such mechanisms as Web Services Description Language (WSDL). The WSDL file associated with a particular channel describes the function that the channel provides and the format of the messages the channel supports. The WSDL file can further be used to attach policies and declare additional constraints and properties to the channel that would otherwise need to be hard-coded into the application.
Further, because the input and output queues used in an ESB can themselves be described as services, they can be discovered just like any other type of service. The client can use a variety of options, from hard-coding the location of request and response queues to using mechanisms such as UDDI or DNS Endpoint Discovery to dynamically resolve services' locations. As we described earlier during our discussion of discovery encapsulation using service brokers, the location of such request-response queues is generally assumed to be relatively static in comparison to the locations of the business services.
Finally, while the asynchronous request-response model would be by far the most common approach to take with an ESB implementation, most ESB implementations support a broad variety of service styles. This includes synchronous request-response services approximating what you typically see with most RPC-style Web services.
Closing points
This paper showed you the general characteristics and philosophy behind the Enterprise Service Bus architectural pattern and how an ESB fits into an integration architecture. The authors asserted that an ESB generally involves nothing more than introducing an intermediary service broker capable of encapsulating many of the specific details of a service interaction, such as the discovery of appropriate provider endpoints and support for differing communications protocols. Further, ESBs provide much more flexible interaction patterns that go well beyond the simple, traditional RPC-style request-response pattern typically associated with Web services. Most importantly, however the ESB does not represent any single new "thing" or "product" that can be packaged, sold, installed, and so on. Rather, it embodies the application of fundamental Service-Oriented Architecture principles to existing middleware design philosophy.
Resources
About the authors
 | 
|  | James Snell is a member of the IBM Software Standards Strategy Group focusing on the prototype development of pre-emerging software technologies and standards. He is currently a member of the Emerging Technologies Toolkit for Web Services and Autonomic Computing development team and has made significant direct and indirect contributions to nearly every aspect of IBM Web services and Service-Oriented Architecture strategy as well as serving as a strong advocate for the practical application of key emerging technologies within and across IBM lines of business. |
Rate this page
|