Over the years, systems have grown significantly in terms of complexity and sophistication. The need to have systems with better reliability, increased scalability, and more flexibility than in the past has given rise to more complex and sophisticated architectures. In response to this increased demand for better and faster systems, architects, designers, and developers have been leveraging messaging as a way of solving these complex problems.
Messaging has come a long way since the first edition of this book was published in 2000, particularly with respect to the Java platform. Although the Java Message Service (JMS) API hasnât changed significantly since its introduction in 1999, the way messaging is used has. Messaging is widely used to solve reliability and scalability issues, but it is also used to solve a host of other problems encountered with many business and nonbusiness applications.
Heterogeneous integration is one primary area where messaging plays a key role. Whether it be through mergers, acquisitions, business requirements, or simply a change in technology direction, more and more companies are faced with the problem of integrating heterogeneous systems and applications within and across the enterprise. It is not unusual to encounter a myriad of technologies and platforms within a single company or division consisting of Java EE, Microsoft .NET, Tuxedo, and yes, even CICS on the mainframe.
Messaging also offers the ability to process requests asynchronously, providing architects and developers with solutions for reducing or eliminating system bottlenecks, and increasing end user productivity and overall system scalability. Since messaging provides a high degree of decoupling between components, systems that utilize messaging also provide a high degree of architectural flexibility and agility.
Application-to-application messaging systems, when used in business systems, are generically referred to as enterprise messaging systems, or Message-Oriented Middleware (MOM). Enterprise messaging systems allow two or more applications to exchange information in the form of messages. A message, in this case, is a self-contained package of business data and network routing headers. The business data contained in a message can be anythingâdepending on the business scenarioâand usually contains information about some business transaction. In enterprise messaging systems, messages inform an application of some event or occurrence in another system.
Using Message-Oriented Middleware, messages are transmitted from one application to another across a network. Enterprise middleware products ensure that messages are properly distributed among applications. In addition, these products usually provide fault tolerance, load balancing, scalability, and transactional support for enterprises that need to reliably exchange large quantities of messages.
Enterprise messaging vendors use different message formats and network protocols for exchanging messages, but the basic semantics are the same. An API is used to create a message, load the application data (message payload), assign routing information, and send the message. The same API is used to receive messages produced by other applications.
In all modern enterprise messaging systems, applications exchange messages through virtual channels called destinations. When a message is sent, itâs addressed to a destination (i.e., queue or topic), not a specific application. Any application that subscribes or registers an interest in that destination may receive the message. In this way, the applications that receive messages and those that send messages are decoupled. Senders and receivers are not bound to each other in any way and may send and receive messages as they see fit.
All enterprise messaging vendors provide application developers with an API for sending and receiving messages. While a messaging vendor implements its own networking protocols, routing, and administration facilities, the basic semantics of the developer API provided by different vendors are the same. This similarity in APIs makes the Java Message Service possible.
JMS is a vendor-agnostic Java API that can be used with many different enterprise messaging vendors. JMS is analogous to JDBC in that application developers reuse the same API to access many different systems. If a vendor provides a compliant service provider for JMS, the JMS API can be used to send and receive messages to that vendor. For example, you can use the same JMS API to send messages using SonicMQ that you would using IBMâs WebSphere MQ. It is the purpose of this book to explain how enterprise messaging systems work and, in particular, how JMS is used with these systems. The second edition of this book focuses on JMS 1.1, the latest version of the specification, which was introduced in March 2002.
The rest of this chapter explores enterprise messaging and JMS in more detail, so that you have a solid foundation with which to learn about the JMS API and messaging concepts in later chapters. The only assumption we make in this book is that you are already familiar with the Java programming language.
As stated at the beginning of this chapter, messaging solves many architectural challenges such as heterogeneous integration, scalability, system bottlenecks, concurrent processing, and overall architecture flexibility and agility. This section describes the more common advantages and uses for JMS and messaging in general.
The communication and integration of heterogeneous platforms is perhaps the most classic use case for messaging. Using messaging you can invoke services from applications and systems that are implemented in completely different platforms. Many open source and commercial messaging systems provide seamless connectivity between Java and other languages and platforms by leveraging an integrated message bridge that converts a message sent using JMS to a common internal message format. Examples of these messaging systems include ActiveMQ (open source) and IBM WebSphere MQ (commercial). Both of these messaging systems support JMS, but they also expose a native API for use by non-Java messaging clients (such as C and C++). The key point here is that, depending on the vendor, it is possible to use JMS to communicate to non-Java or non-JMS messaging clients.
Historically, there have been many ways of tackling the issue of heterogeneous systems integration. Some earlier solutions involved the transfer of information through FTP or some other file transfer means, including the classic âsneakernetâ method of carrying a diskette or tape from one machine to another. Using a database to share information between two heterogeneous systems or applications is another common approach that is still widely used today. Remote Procedure Call, or RPC, is yet another way of sharing both data and functionality between disparate systems. While each of these solutions have their advantages and disadvantages, only messaging provides a truly decoupled solution allowing both data and functionality to be shared across applications or subsystems. More recently, Web Services has emerged as another possible solution for integrating heterogeneous systems. However, lack of reliability for web services make messaging a better integration choice.
System and application bottlenecks occur whenever you have a process that cannot keep up with the rate of requests made to that process. A classic example of a system bottleneck is a poorly tuned database where applications and processes wait until database connections are available or database locks free up. At some point the system backs up, response time gets worse, and eventually requests start timing out.
A good analogy of a system bottleneck is pouring water into a funnel. The funnel becomes a bottleneck because it can only allow a certain amount of water to pass through. As the amount of water entering the funnel increases, the funnel eventually overflows because water cannot exit the funnel fast enough to handle the increased flow. IT systems work in much the same way: some components can only handle a limited number of requests and can quickly become bottlenecks.
Going back to our example, if a single funnel can âprocessâ one liter of water per minute, but three liters of water are entering the funnel, the funnel will eventually back up and overflow. However, by adding two more funnels to the process, we can now theoretically âprocessâ three liters of water per minute, thereby keeping up with the demand. Similarly, within IT systems messaging can be used to reduce or even eliminate system bottlenecks. Rather than have requests backing up one behind the other while a synchronous component is processing them, the requests are sent to a messaging system that distributes the requests to multiple message listener components. In this manner the bottlenecks experienced with a single synchronous point-to-point connection are reduced or in some cases completely eliminated.
Much in the same way that messaging reduces system bottlenecks, it can also be used to increase the overall scalability and throughput of a system, effectively reducing the response time as well. Scalability in messaging systems is achieved by introducing multiple message receivers that can process different messages concurrently. As messages stack up waiting to be processed, the number of messages in the queue, or what is otherwise known as the queue depth, starts to increase. As the queue depth increases, system response time increases and throughput decreases. One way to increase the scalability of a system is to add multiple concurrent message listeners to the queue (similar to what we did in the funnel example previously) to process more requests concurrently.
Another way to increase the overall scalability of a system is to make as much of the system asynchronous as possible. Decoupling components in this manner allows for systems to grow horizontally, with hardware resources being the main limiting factor. However, while this may seem like a silver bullet, the middleware can only be horizontally scaled within practical limits of another major system bottleneckâthe database. You can have hundreds or even thousands of message listeners on a single queue providing the ability to process many messages at the same time, but the database may only be able to process a limited number of concurrent requests. Although there are complicated techniques for addressing the database bottleneck issue, the reality is that there will always be practical limits to how far you can scale the middleware layer.
The use of asynchronous messaging can also increase end user productivity. Consider the case where an end user makes a request to the system from a web-based or desktop user interface that takes several minutes to run. During that time the end user is waiting for the results, unable to do any additional work. By using asynchronous messaging, the end user can make a request to the system and get an immediate response back indicating that the request was accepted. The end user now continues to do other work on the system while the long running request is executing. Once the request has completed, the end user is notified that the request has been processed and the results are delivered to the end user. By using messaging, the end user is able to get more work done with less wait time, making that end user more productive.
Many front-office trading systems use this sort of messaging strategy between the trading application and the backend systems. This type of messaging-based architecture allows the trader to perform other work without having to wait for a response from the system. The trade-off for this increased flexibility and productivity, however, is added complexity. A good architect will always look for opportunities to make various aspects of a system asynchronous, whether it be between a user interface and a system or between internal components within the system.
The use of messaging as part of an overall enterprise architecture solution allows for greater architectural flexibility and agility. These qualities are achieved through the use of abstraction and decoupling. With messaging, subsystems, components, and even services can be abstracted to the point where they can be replaced with little or no knowledge by the client components.
Architectural agility is the ability to respond quickly to a constantly changing environment. By using messaging to abstract and decouple components, one can quickly respond to changes in software, hardware, and even business changes. The ability to swap out one system for another, change a technology platform, or even change a vendor solution without affecting the client applications can be achieved through abstraction using messaging. Through messaging, the message producer, or client component, does not know which programming language or platform the receiving component is written in, where the component or service is located, what the component or service implementation name is, or even the protocol used to access that component or service. It is by means of these levels of abstraction that we are able to more easily replace components and subsystems, thereby increasing architectural agility.