JaVa
   

Deciding when to Use Asynchronous Calling with JMS

So far we've considered only synchronous functionality, and only remote communication using RMI/IIOP. J2EE also allows us to implement asynchronous functionality, and to use messaging as an alternative form of communication with other apps. We may need to use messaging to meet some business requirements, or we may choose to use messaging if we believe that an asynchronous model is the best implementation option. In this section we'll consider some of the architectural and implementation choices relating to messaging.

Message-Oriented Middleware (MOM) and JMS

MOM is infrastructure that supports messaging: loosely-coupled, standards-based and usually asynchronous communication between components or apps. Messaging involves a message broker that sits between message producers (equivalent to method callers in a synchronous model) and message consumers (objects that listen for and process messages). There may be multiple message consumers. A message producer can normally continue with its work after publishing a message: unlike a synchronous method invocation, publishing a message does not block. This is sometimes called fire-and-forget.
Until recently, MOM has not been widely used in J2EE. However, its value has been proven in many other systems. Different messaging systems traditionally have proprietary APIs. The Java Message Service (JMS) is a standard J2EE API for messaging, which sits on a Service Provider Interface (SPI) that can be implemented for different messaging systems. Like JDBC, JMS abstracts the details of low-level access using proprietary conventions. app code that uses JMS doesn't need to concern itself with the underlying messaging system, or the message transport it uses. JMS supports both Publish and subscribe (Pub/Sub), and Point-to-Point (PTP) messaging. The JMS API is available to EJBs, servlets and app clients. J2EE 1.3 integrates the JMS into the EJB model, enabling the EJB container to handle some low-level JMS API issues. Message-driven beans provide special support for JMS message consumers.

We can make full use of JMS in any component managed by a J2EE server. However, EJBs are especially well-equipped both to produce and consume messages.

Producing Messages

Any object running within a J2EE server can produce JMS messages. EJBs benefit from no special JMS-related services for JMS message publication. Like other objects running within a J2EE server, they must use JNDI and the JMS API to obtain a JMS connection and session before publishing messages. However, message production – like resource management – can benefit from CMT in the EJB tier. Container-managed transactions can provide transparent transaction management for transacted JMS sessions. JMS supports the notion of transactions. However, it's important to distinguish between JMS transactions and JTS (that is, ordinary J2EE) transactions. A transacted JMS session enables us to batch messages. When creating a JMS session (which we need to do before sending messages in a message producer), we must specify whether or not a session should be transacted. If a session is transacted, either the session's commit() or rollback() method should be called to let JMS know what to do with any buffered messages. In the event of a commit they will be delivered as a single unit (all delivered atomically); in the event of a rollback, they will all be discarded. It's possible to work with different JMS session objects if some messages must be transacted while others need not be. Enterprise resources such as databases are not enlisted in JMS transactions. Resource enlistment is the business of ordinary JTS transactions. JMS transactions are local transactions with a special meaning. It is possible for JTS transactions to enlist JMS resources, so it's usually preferable for message producers to work with JTS transactions (which can, of course, be managed by the container in the EJB tier) than with JMS transactions directly.

If message production is not transactional (for example, if only one message is published per operation, or if each message has to be processed individually), there's no compelling reason to use EJBs as message producers.

Consuming Messages

It is in consuming messages that EJBs really shine.

Consuming Messages without Using EJB

Any component running within an environment managed by the J2EE server can consume JMS messages. However, implementing a message consumer involves using JNDI to look up a JMS destination and registering a listener with it; the JNDI and JMS code involved is fairly complex. While we can use generic infrastructure code to remove these responsibilities from app code, it's better to use standard infrastructure offered by the EJB container, if possible.

Note 

We'll discuss generic infrastructure code to simplify the use of JMS in .

Consuming Messages with Message-Driven Beans (MDB)

Consuming messages with MDBs is much easier, as we can specify the message destination and filter declaratively in the ejb-jar.xml deployment descriptor, and concentrate on handling messages, not writing low-level JMS or JNDI code. Let's consider some of the capabilities of MDBs, and some important implementation considerations.

MDB Concepts

A MDB is an asynchronous message consumer with access to EJB container services. An MDB is not visible to a client. Unlike session or entity beans, MDBs don't have home or component interfaces; instead, MDB instances are invoked at runtime by an EJB container on receipt of a JMS message. The EJB container knows which MDB to invoke on receipt of a message, based on a declarative mapping in EJB deployment descriptors. MDBs can consume messages from either topics (in Pub/Sub messaging) or queues (in PTP messaging). The EJB container, not the bean developer, handles JMS message acknowledgment for MDBs; the developer must not use the JMS API to acknowledge messages.

Important 

As the EJB container provides valuable services to MDBs, MDBs are usually the best choice for writing message consumers when using JMS in a J2EE app.

Interfaces

An MDB must implement two interfaces: javax.ejb.MessageDrivenBean, and javax.*** MessageListener. The MessageDrivenBean interface is analogous to the Session*** and EntityBean interfaces, with which it shares the EnterpriseBean superinterface. It defines the callback methods the EJB container uses to manage an EJB instance's lifecycle. The MessageListener interface is part of the JMS API, rather than the EJB API, and includes a single method, onMessage(), which is invoked when the EJB container receives a message relevant to the MDB instance.

Note 

Why doesn't the MessageDriven interface simply extend the MessageListener interface? By keeping the interfaces separate, the EJB specification makes explicit the distinction between message handling infrastructure and the nature of the messages being processed. This reserves the option of future versions of the EJB specification to use the same approach to handling messages from different sources – for example, e-mail messages or Java API for XML-based Messaging (JAXM) messages.

The onMessage() method is responsible for all business operations in an MDB. Unlike session bean business methods, this method is weakly typed. The message may be of a type that the MDB doesn't know how to handle, although declarative filtering should reduce the number of unwanted messages. Like stateless session beans, MDBs must implement a single, no-argument ejbCreate() method. Note that as with stateless session beans, this method is not required by the relevant interfaces, so failure to provide it will only be evident at deployment time. In we'll discuss a convenient, generic superclass we can use for MDBs to help meet the requirements of the EJB specification.

Important 

It is the responsibility of an MDB to ensure that it can handle the messages it "opens". Runtime tests should be performed to avoid class cast exceptions at runtime.

Programming Restrictions Applying to MDBs

The normal EJB coding restrictions apply to MDBs as well. For example, MDBs cannot create threads on use synchronization. MDBs have most in common with stateless session beans. They can't hold conversational state. They can be pooled in the app server, as all instances of the same MDB are equivalent. There are a few additional restrictions unique to MDBs. Exception handling differs from that of other EJB types because MDBs have no client. of the EJB specification states that MDBs must not throw app exceptions or java.rmi.RemoteException. A runtime exception such as an EJBException or an uncaught throwable should only be thrown if the bean encounters an unexpected fatal exception. Such an exception will prompt the EJB container to try to redeliver the message, potentially resulting in the same problem being repeated. A message causing this scenario is called a poison message, although the problem is more likely to lie in the MDB's implementation than the message itself.

Since an MDB is decoupled from the message sender (or client), it cannot access the security information normally available through the EJB's context at runtime in the getCallerPrincipal() and isCallerInRole() methods. This limits the use of messaging in practice, as this restriction is sometimes unacceptable (for example, business rules may differ depending on the identity and permissions of the user attempting to perform an operation).

Transactions and MDBs

Like all EJBs, MDBs can use the JTS transaction management services of the EJB container. We've already discussed JMS transactions, which are a distinct issue. However, transactions work differently with MDBs, compared to other types of EJB.

Important 

MDBs cannot participate in clients' transactions.

Like session beans, MDBs can be used with either CMT or BMT. When using CMT, the only legal transaction attributes to specify in the ejb-jar.xml deployment descriptor are Required and NotSupported. If the transaction attribute is Required, the EJB container starts a new transaction and invokes the MDB's onMessage() method within it. The transaction is committed when this method returns. The message receipt is part of the transaction created by the container. If the transaction aborts (due to a runtime exception or a call to the MessageDrivenContext's setRollbackOnly() method) the message remains in the JMS destination and will be redelivered. This is an important point; it means that if an MDB with CMT and a transaction attribute of Required rolls back or aborts a transaction due to an error, subsequent deliveries of the poison message may cause the same problem. Consider the case of sloppy code that fails to check the type of a message before trying to cast it to a TextMessage. This will result in a runtime class cast exception – an error that will occur over and over again as the EJB container redelivers the message to the same MDB, although not the same instance. We can avoid this problem by taking extra care that MDBs don't throw runtime exceptions at runtime.

Important 

MDBs should not indicate an app error by throwing an exception or rolling back the current transaction. This will simply cause the message to be delivered again, in which case the failure will be repeated (and repeated). A better alternative is to publish an error message to a JMS error destination.

Only in the case of a fatal (but possibly transient) system-level failure, should a runtime exception be thrown or the current transaction to be rolled back, meaning that the message will be redelivered. The latter behavior might be appropriate, for example, in the event of a failure originating from a database. The database might be temporarily down, in which case it makes sense to have another attempt at processing the message, when the underlying problem may have been resolved.

When using CMT with a transaction attribute of NotSupported, the EJB container will not create a transaction before calling the MDB's onMessage() method. In this case, message acknowledgment will still be handled automatically by the container.

When using BMT, transactional boundaries must be set by the bean developer inside the onMessage() method. This means that the message receipt is not part of the transaction. The message will not automatically be redelivered if the bean-managed transaction rolls back. However, acknowledgment is still handled by the EJB container, rather than the MDB developer. The bean developer should indicate in the acknowedge-mode ejb-jar.xml deployment descriptor element whether JMS AUTO_ACKNOWLEDGE semantics or DUPS_OK_ACKNOWLEDGE semantics should apply (JMS AUTO_ACKNOWLEDGE is the default). DUPS_OK_ACKNOWLEDGE will incur less JMS overhead.

Subscription Durability

MDBs to be used with a topic (for Pub/Sub messaging) can be marked as using a durable subscription in the optional <subscription-durability> element in the ejb-jar.xml deployment descriptor. The EJB container will transparently handle the details of the durable subscription (such as subscriber identification to the JMS server). If this element is not supplied, subscription is non-durable.

When to Use Asynchronous Calling

Now that we are familiar with the major issues concerning JMS and MDBs, we can consider when to use messaging.

Indications for Using Messaging

Asynchronous calling can only be used when the caller doesn't need an immediate response. Thus, a method that has a return value isn't a candidate for asynchronous calling. Nor is a method that may throw an app exception. Checked exceptions provide alternative return values for Java methods, and force clients to take action. This isn't possible with asynchronous calling. Assuming that these essential conditions hold, the following indications suggest the use of JMS. The more of these indications that apply, the more appropriate the use of messaging is likely to be:

Disadvantages of Using Messaging

Using JMS is likely to bring the following disadvantages:

Thus, don't take the decision to adopt JMS lightly (even if the situation suggests that it's appropriate to use messaging).

JMS and Performance

Messaging is often advocated for performance reasons. However, if messaging is to achieve the same degree of reliability as RMI (which at least guarantees that the client knows if something goes wrong on the server), it's unlikely to achieve any performance benefit. The overhead of JMS varies greatly depending on how it is used: acknowledgment modes, subscription durability etc. Guaranteed delivery (necessary for some operations) is likely to be slow. Imagine a system that queues requests to create user accounts, performing the actual database inserts asynchronously. To be sufficiently reliable, this would have to use guaranteed delivery, meaning that the app server would need to store the messages in a database or other persistent store to ensure that the messages were delivered. Thus the EJB container would probably end up doing a database insert when the message was published anyway, meaning that there would be no performance benefit even in the short term, and increased cost overall. Only if we can use messaging without guaranteed delivery or if the asynchronous operation is extremely slow is messaging likely to deliver performance gains.

Important 

Do not use messaging without good reason. Using messaging inappropriately will make a system more complex and error-prone, add no value, and probably reduce performance.

Alternatives to JMS Messaging

In some situations there may be simpler, lighter-weight alternatives to JMS, using thread creation (allowed outside the EJB container) or the Observer design pattern without JMS (that is, notifying listeners – who may perform actions in the calling thread or in a new thread – using an ordinary Java implementation, and not JMS). The Observer design pattern implemented this way will create much less overhead than JMS. However, whether it works in a clustered environment depends on what listeners must do. The server provides cluster-wide support for JMS; clearly we don't want to build our own cluster-capable Observer implementation, when JMS already delivers this. Situations in which simple implementations of the Observer design pattern are a viable alternative to JMS include:

JMS in the Sample app

No part of the sample app's functionality is asynchronous. All user actions must result in synchronous method invocation. However, it is plausible to use JMS to attempt to improve performance by invalidating cached data in response to events generated when something changes in the system. For example, if a user tutorialed a seat for a particular performance, cached availability details for this performance might be updated across a cluster of servers. This approach would push information about data updates to caches, rather than leave them to pull new information after a given timeout. Messages might also be sent on other events including cancellation of a tutorialing or the entry of a new performance or show. This would use JMS as a cluster-capable implementation of the Observer design pattern. Whether this will improve performance will depend on how significant the JMS overhead proves and whether a synchronous alternative (such as refreshing cached data on request if it is deemed to have timed out) will prove adequate. The asynchronous approach has the disadvantage of making the app harder to deploy and test. Such questions, which have significant architectural implications, should be resolved as early as possible: usually by implementing a vertical slice of the app's functionality early in the project lifecycle. In this case, a vertical slice will enable us to run benchmarks to establish whether a simple cache will meet our performance requirements (it's best to test the simpler approach first, as if it proves satisfactory we will never need to implement the more complex approach). This benchmark is discussed in . The conclusion is that, where seat availability data is concerned, a cache can produce very good performance results, even if set to time data out at 10 seconds. Thus incurring the overhead ofJMS messaging every time a tutorialing is made is unnecessary. The fact that reservations can time out in the database if the user doesn't proceed to purchase is also problematic for aJMS approach to availability updates.

It's a different matter with reference data about genres, shows and performances. This data takes much longer to retrieve from the database, and changes rarely. Thus publishing a JMS message is a very good approach. We will use JMS in the sample app only to broadcast reference data updates.

JaVa
   
Comments