Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Publish, Subscribe, & the JMS API


Jul00: Publish, Subscribe, & the JMS API

Phil and Dan develop systems management software for Tivoli Systems in Austin, Texas. They can be reached at [email protected] and dangreff@ yahoo.com, respectively.


Middleware is an essential tool in building enterprise class distributed systems. A middleware system provides an array of communication services, which are used to link application components running across a local or wide area network (see Figure 1). This liberates application programmers from being concerned with many of the details of distributed programming (guaranteed delivery, transaction semantics, retry protocols, and the like) and lets them concentrate on the particulars of their applications.

As the number of mature middleware systems increases, a wider variety of distributed programming semantics are being supported. The most common APIs (CORBA, RMI, DCE) offer some flavor of remote procedure call (RPC). While these APIs provide familiar semantics, this one-to-one synchronous model of component communication is awkward for many distributed applications.

Two middleware API standards supporting message passing rather than RPC semantics have been introduced -- the Application Messaging Interface (AMI) from the Open Application Group (http://www .openapplications.org/) and the Java Message Service (JMS) (http://java.sun.com/ products/jms/) from Sun Microsystems. Both standards support a publish/subscribe, and a point-to-point semantic for connecting distributed components. We have found the publish/subscribe paradigm useful for a variety of applications. In this article, we will explore the advantages of publish/subscribe distributed programming by discussing a chatroom application we implemented using JMS APIs (Figure 2). The chatroom runs on top of the IBM MQSeries JMS implementation. Use of the JMS API makes porting the programs between JMS implementations reasonably simple.

Publishers, Subscribers, and Topics

The fundamental concepts of the JMS publish/subscribe API are straightforward. Subscribers register their interest in particular messages with the middleware. Publishers register their intent to publish certain types of messages. When a message is published, it is the middleware's responsibility to efficiently route it to all interested subscribers. Uniting particular publishers and subscribers are topics. Every subscriber must subscribe to a topic. Every publisher must publish on a topic. The middleware can use its knowledge of the location of each topic's publishers and subscribers to optimize message routing.

In our chatroom application, each chatroom is modeled as a JMS Topic. Topics are one of only two objects in the JMS publish/subscribe API whose creation semantics are not completely specified by the API. (The other, TopicConnectionFactory, will be discussed later.) Our chatroom application currently runs only with the IBM MQSeries JMS implementation. We have placed all our code, which relies on features of MQSeries, in the chatdemo .mqseries package (available electronically; see "Resource Center," page 5). The only class that interacts with objects in the chatroom.mqseries package is chatdemo.JMSProvider. Therefore, porting the application to a different JMS implementation would simply entail developing another package equivalent to chatroom.mqseries (which contains only 25 lines of code) and updating chatdemo.JMSProvider to interface with the new package. Also, the HTML file that initializes the chatroom would require one change.

Just as publishers, subscribers, and topics are the central concepts of JMS, the high-level structure of most JMS applications can be framed in these terms. This is the case for our chatroom application. Each chatroom has a corresponding JMS Topic. Each participant in the chatroom has a Publisher and Subscriber object associated with the Topic. The flow of messages published on the topic is used to communicate arrivals, departures, and messages.

When the application initializes, the list of chatroom names is passed to the chatdemo.mqseries.JMSProviderImpl.createTopics() method, which uses MQSeries-specific logic to build a corresponding set of Topics. When the user selects a chatroom to enter, the corresponding Topic is passed to chatdemo.chatroom.ChatRoom.join() where TopicPublisher and TopicSubscriber objects are obtained. Once the Publisher and Subscriber objects are in place, a message is published announcing the user's entry. If the user is leaving one chatroom and entering another, the join() method first calls the leave() method to publish a departure message to the old chatroom and close the existing TopicPublisher and TopicSubscriber.

Messages and Filters

The data structures that flow between publishers and subscribers are instances of JMS's Message interface. Every JMS Message carries a set of name value pairs called "properties." JMS requires each Message to carry a minimal set of properties (like a time stamp and message identifier) that are set by the middleware after publication. Applications can also add their own properties. In the sendMessage() method of our ChatRoom class, we add the properties targetUser, sender, and messageType to each message.

When a JMS client registers a subscription with the middleware, in addition to specifying a Topic, they can also pass a filter String. The syntax of a filter String is similar to that of a SQL Where clause. The filter string references Message properties and is applied to each Message published on the Subscriber's topic. Only Messages that satisfy the filter are actually delivered. Our chatroom application uses filters extensively (see chatdemo.chatroom.ChatRoom.generateFilter()). For instance, when a user with the ID phil enters a chatroom the filter String on his subscription will be:

(targetUser = 'phil') OR (targetUser = 'all')

This tells the middleware to only deliver messages that are directed to the entire room or are directed to this particular client. This filter supports a simple and efficient implementation of a "private message" function (not currently included in our GUI). At publication, the targetUser property of messages intended for the entire room is set to "all" while a specific user ID can be set for messages sent as private messages. This ensures that the middleware only delivers messages to a client if they are actually going to be displayed.

We also use JMS's message filtering function to implement our chatroom's "ignore user" feature. In the case of user phil, who does not want to see messages posted by two other users, say dan and harry, the createFilter() method builds a more complex filter like:

((targetUser = 'phil') OR (targetUser = 'all'))

AND ((sender NOT IN ('dan', 'harry') OR (messageType <> 0))

A message type of 0 indicates a regular chatroom message. Other message types are used when a user enters or leaves a room. This filter will prevent phil from seeing messages sent by dan or harry, but will permit him to receive notification when they leave the room.

JMS provides several interfaces that extend Message. These extensions add a payload to the Message. We have found the ObjectMessage extension to be most useful. It allows a Serializable object to be attached to a message, in addition to the message's properties. The contents of the payload object are not examined by the middleware, and therefore cannot be referenced in a subscription filter. We use the ObjectMessage payload to pass the text of our chatroom messages and other information.

Connections, Connection Factories, and Sessions

Besides Publishers, Subscribers, Topics, and Messages, three other objects that a JMS publish/subscribe programmer needs to be cognizant of are the TopicConnectionFactory, TopicConnection, and TopicSession. These objects are used mainly to pass control and context information between the JMS client and the middleware. As noted earlier, the semantics for obtaining a TopicConnectionFactory are provider specific. To increase application portability, it is best to isolate creation of TopicConnectionFactorys and Topics to a single class. In our chatroom application, that class is chatdemo.mqseries.JMSProviderImpl. We further improved portability by encapsulating references to the chatroom.mqseries.JMSProviderImpl class to chatroom.JMSProvider. This will enable us to easily add logic to chatroom.JMSProvider to invoke a variety of JMSProviderImpls so that the choice of middleware can be made at run time.

Once a TopicConnectionFactory and TopicConnection have been obtained, TopicSessions can be created by calling the TopicConnection's createSession() method. TopicSessions are not thread safe, so each application thread should reference its own TopicSession. TopicPublisher and TopicSubscriber objects are obtained from the TopicSession (again, please see chatdemo.chatroom.ChatRoom.join() for examples of publisher and subscriber creation).

One notable limitation of the JMS publish/subscribe API is that a TopicSubscriber's filter must be specified at the time it is created by the TopicSession. It is not possible to change a subscriber's filter. To accomplish a change of filter, the old subscriber must be closed and a new one, with the updated filter, created (see chatdemo.chatroom.ChatRoom.applyFilter). Depending upon how this task is coded, messages may be lost, or duplicate messages may be received. If both these side effects are unacceptable, considerable logic may be required to change filters safely.

Enabling a Client/Client Programming Model

One advantage of publish/subscribe programming is that many (and sometimes all) of the functions normally performed by the server components of a client/server application can be performed by the publish/subscribe middleware instead. Our chatroom example is a good illustration of this. The entire application was implemented by coding only one client component. Most distributed applications require both client and server components. N-tier applications may also require intermediate (or gateway) components. However, publish/subscribe applications can often employ a single component, peer-to-peer, design. As we will show in the next section, with careful middleware deployment such applications can be as scalable as N-tier client/server systems.

Programming to standardized publish/subscribe APIs leads to a two-phase implementation process. One activity involves designing and implementing an application consisting of cooperating publish/subscribe clients. These clients pass messages through a middleware "cloud" (Figure 3). Application components are not concerned with the number, location, configuration, or interconnection of middleware servers. The application simply relies on the middleware to deliver messages to interested subscribers with adequate performance (as defined by the application). Selecting, installing, and configuring the middleware is a separate activity that programmers need only be minimally involved with. Specifically, the application developer must clearly document how much message traffic each client will generate, how big the messages are expected to be, and what the maximum tolerable latency is. Once these parameters are defined, it is the responsibility of the middleware implementor to construct a middleware installation that will provide adequate performance to the application.

Scalable Publish/Subscribe Middleware

A publish/subscribe application paradigm is not only a natural programming style for many applications, it also eases the construction of efficient server topologies. The main reason for this is that publish/subscribe applications can be efficiently deployed in hierarchical server networks. Consider the application/server topology in Figure 4, which shows five clients using two topics. Whenever a publish/subscribe client registers or deregisters on a topic (which, in a well-designed application, should happen relatively infrequently), that registration information can be propagated to all servers. Further, in a JMS server network, the filters provided by subscribers on each topic can be cached at the servers hosting publishers on those topics. Filters can then be applied at the publisher's node and messages that prove to not be relevant to any subscriber can be discarded before they consume unnecessary bandwidth.

Again, returning to Figure 4, because both clients on Topic A are on adjacent servers, message traffic on that topic only consumes bandwidth between those two servers. Also, regardless of how many subscribers need to receive any given message, no more than one copy of the message need flow between any two servers. For instance, suppose Client 2 publishes a message that satisfies the filters of both Clients 4 and 5. Then one copy of the message is transferred from Servers 5 to 2 to 1 to 3 to 7. When the message reaches Server 7, it is delivered to both subscribers. In other words, publish/subscribe server networks lend themselves to efficient use of multicast network technologies.

It is worth emphasizing that these optimizations of the transport network are transparent to the application and the application programmer. Our chatroom application views the JMS infrastructure as a single publish/subscribe cloud, as in Figure 3. The application is independent of the server topology that supports it.

Using a programming model that is well suited to hierarchical n-tiered server networks is important for enterprise-class applications. Hierarchical server topologies are the most scalable. A hierarchical server topology with a fan-out of 100 child servers per parent server can support over a million servers with only three tiers. Such a configuration would allow a maximum of four hops between any publisher and subscriber.

Selecting Publish/Subscribe APIs and Middleware

Mastering publish/subscribe programming will be important for many distributed systems specialists because it is such a powerful tool for building scalable applications. As mentioned, JMS is not the only standardized publish/subscribe middleware API. Competing implementations of the Open Application Group's AMI may soon be available. Unlike JMS, which is a Java-only API, AMI is language neutral. This should make it attractive for applications where a purely Java solution is not preferred. In addition to JMS and AMI, many middleware vendors support their own proprietary publish/subscribe APIs. These proprietary alternatives may be appropriate in situations where application portability is not an issue.

All these factors make the probability of successfully implementing and deploying a scalable publish/subscribe application quite high. The application designer can select the middleware API that lends itself best to the application. If a standard API such as JMS or AMI is selected, then there may be a wide variety of middleware solutions available at deployment time. Experience has shown that, while having competing APIs each with competing implementations may complicate programmer's jobs, it does often lead to solid application implementations.

DDJ


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.