MDB best practices (EJB 3)

Like all technologies, MDBs have some pitfalls to watch for and some best practices that you should keep in mind. This is particularly true in demanding environments where messaging is typically deployed.

Choose your messaging models carefully. Before you wade knee deep in code, consider your choice of messaging model carefully. You might find that PTP will solve your problem nine times out of ten. In some cases, though, the pub-sub approach is better, especially if you find yourself broadcasting the same message to more than one receiver (such as our system outage notification example). Luckily, most messaging code is domain independent, and you should strive to keep it that way. For the most part, switching domains should be just a matter of configuration.

Remember modularization. Because MDBs are so similar to session beans, it is natural to start putting business logic right into message listener methods. Business logic should be decoupled and modularized away from messaging-specific concerns. We followed this principle by coding the processShippingRequest method and invoking it from onMessage. An excellent practice (but one that would have made this topic unnecessarily complicated) is to put business logic in session beans and invoke them from the onMessage method.

Make good use of message filters. There are some valid reasons for using a single messaging destination for multiple purposes. Message selectors come in handy in these circumstances. For example, if you’re using the same queue for both shipping requests and order cancellation notices, you can have the client set a message property identifying the type of request. You can then use message selectors on two separate MDBs to isolate and handle each kind of request.


Conversely, in some cases, you might dramatically improve performance and keep your code simple by using separate destinations instead of using selectors. In our example, using separate queues and MDBs for shipping requests and cancellation orders could make message delivery much faster. In this case, the client would have to send each request type to the appropriate queue.

Choose message types carefully. The choice of message type is not always as obvious as it seems. For example, it is a compelling idea to use XML strings for messaging. Among other things, this tends to promote loose coupling between systems. In our example, the Turtle server would know about the format of the XML message and not the ShippingRequest object itself.

The problem is that XML tends to bloat the size of the message, significantly degrading MOM performance. In certain circumstances, it might even be the right choice to use binary streams in the message payload, which puts the least amount of demand on MOM processing as well as memory consumption.

Be wary of poison messages. Imagine that a message is handed to you that your MDB was not able to consume. Using our example, let’s assume that we receive a message that’s not an ObjectMessage. As you can see from this code snippet, if this happens the cast in onMessage will throw a java.lang.ClassCastException:

tmp24148_thumb

Since onMessage will not complete normally, the container will be forced to roll back the transaction and put the message back on the queue instead of acknowledging it (in fact, since a runtime exception is thrown, the bean instance will be removed from the pool). The problem is, since we are still listening on the queue, the same message will be delivered to us again and we will be stuck in the accept/ die loop indefinitely! Messages that cause this all-too-common scenario are called poison messages.

The good news is that many MOMs and EJB containers provide mechanisms that deal with poison messages, including "redelivery" counts and "dead message" queues. If you set up the redelivery count and dead message queue for the shipping request destination, the message delivery will be attempted for the specified number of times. After the redelivery count is exceeded, the message will be moved to a specially designated queue for poison messages called the "dead message" queue. The bad news is that these mechanisms are not standardized and are vendor specific.

Configure MDB pool size. Most EJB containers let you specify the maximum number of instances of a particular MDB the container can create. In effect, this controls the level of concurrency. If there are five concurrent messages to process and the pool size is set to three, the container will wait until the first three messages are processed before assigning any more instances. This is a double-edged sword and requires careful handling. If you set your MDB pool size too small, messages will be processed slowly. At the same time, it is desirable to place reasonable limits on the MDB pool size so that many concurrent MDB instances do not choke the machine. Unfortunately, at the time of this writing, setting MDB pool sizes is not standardized and is provider specific.

Summary

In this topic, we covered basic messaging concepts, JMS, and MDBs. Messaging is an extremely powerful technology for the enterprise, and it helps build loosely coupled systems. JMS allows you to use message-oriented middleware (MOM) from enterprise Java applications. Using the JMS API to build a message consumer application can be time consuming, and MDBs make using MOM in a standardized manner through Java EE extremely easy.

Note, however, that messaging and MDBs are not right for all circumstances and can be overused. One such case is using the request/reply model (discussed in the sidebar "The request-reply model"), which entails a lot of extra complexity compared to simple PTP or pub-sub messaging. If you find yourself using this model extensively and in ways very close to synchronous messaging, it might be worth thinking about switching to a synchronous technology such as RMI, SOAP, or remote session bean calls.

A few major EJB features we touched on in this topic are dependency injection, interceptors, timers, transaction, and security. As you’ve seen, EJB 3 largely relieves us from these system-level concerns while providing extremely robust and flexible functionality. We’ll discuss dependency injection, timers, and interceptors in the next topic.

Next post:

Previous post: