Thursday, 17 March 2011

Understanding MOM


I was recently tasked with setting up a development environment for some developers to allow for rapid queue development. It's an enterprise environment with a fair amount of integration points, so things can get a little hairy. This got me thinking (as most EE developers do) about that dual edged sword that we are always presented with in the java world, choice.

Making a good choice in the java world means being informed. But isn't the JEE specification supposed to be a cure for weak or inappropriate java technology? The idea is to provide an air-tight specification for all JEE development through the API - so that no matter what provider we choose for any technology, we always end up getting relatively the same ROI. Well, that's the idea. I guess there are a few "uncertified" providers out there (I try look out for "JEE x.x compliant/certified"). Guys who might sell their product as JEE compliant, but with a few tweaks that'll ensure something breaks when you need portability. Sure that's not the goal, the goal is to ADD value rather than subtract it from the API - but that's kind of like the candy in the back of the dodgy looking van, sold to you by the man in the trench-coat. Anyone who's been bitten on portability because of vendor tie-ins will be very weary of that van.

Enough about the abuse of the API and back to enterprise messaging. What is JMS and why do i need it? If you're working in the JEE world it most like means you're working for a medium to large sized corporate. The reason they chose JEE in the first place was because of its ability to address problems in the enterprise space. Sure scalablity, reliability etc are valid reasons but Microsoft offer great capabilities when it comes to this too. The core reason i believe we can't avoid JEE in the corporate space is its enterprise integration capabilities. Systems need to communicate across sometimes vast and chasmic channels and we need solutions which can adapt quickly and effectively to facilitate this communication in a reliable and appropriate manner. This is where enterprise messaging proves to be a key player.

The Java Messaging Service was developed as a communication solution which not only allows asynchronous messaging, but offers a very loosely coupled model too. The idea is to allow systems to negotiate with each other in a 'relaxed' manner which can respond quickly to change without the need to update interfaces, definitions, endpoints etc. (yes webservices, i'm looking at you). It's a very simple API to work with and the abstractions are incredible useful. For more information on the API take a look here:
http://download.oracle.com/javaee/1.3/jms/tutorial/1_3_1-fcs/doc/basics.html

Message Oriented Middleware (MOM) is a great solution for asynchronous, decoupled communication. If you need to send hundreds of notifications to a system every second it for a set period (think twitter, facebook) you may want to consider an asynchronous solution. With JMS, the caller returns immediately and can continue its work without needing to process the message immediately. The performance benefits of this approach can be phenomenal in high-load environments.

Now JMS can be used to send messages of varying types (Text, Object, Map, Stream, Bytes) which illustrates just how flexible this piece of technology is. The distinction between Queues and Topics expands on this flexiblity accomodating both Peer-To-Peer and a publish/subscribe models. One of the common questions that i encounter while selling JMS as an integration solution is around reliablility and persistence.

There is the concept of a 'durable subscriber/message' and a 'persistent message'. A durable message is a message that the JMS server will 'hold onto' (either via file or database persistence) until the topic subscriber becomes available. In this way it is a contract between a particular subscriber, and the JMS Server. This caters for situations where a subscriber goes down for a period of time but has the requirement to process any pending messages when it comes back up. I.E Messages are guaranteed to be delivered once. This is only applicable to the publish/subscribe domain.

A persistent message however applies to the peer-to-peer domain too. This refers specifically to the relationship between the JMS server and the message producer. Upon receipt of a message, the JMS server will first persist the message and then deliver it.

With these concepts of durable subscribers and persistent messages, the JMS reliability and traceability starts looking a lot more attractive to prospective developers. And bare in mind these are just the API basics. JMS providers like Tibco provide a lot more in terms of auditability, traceability and failover. (http://www.scribd.com/doc/16320270/TIBCO-EMS-Guidelines-and-Standards-v1)
There are a few key players when it comes to implementations or providers of JMS. The ones i've had exposure to are Tibco, ActiveMQ and Weblogic JMS. Each have their own value-adds, but it seems that there are scenarios where one is preferred over another and i'll try highlight this to the best of my knowledge.

There is a great 'confluence' tool called HermesJMS (http://www.hermesjms.com/confluence/display/HJMS/Home) which allows you to connect and browse JMS queues and topics through a variety of providers. This is particularly useful in your local dev environment when you need to test your JMS setup or debug your queues. You can publish, send, browse, search, delete from queues and topics using this tool.

I decided to use the ActiveMQ provider with a local Weblogic instance to act as my JMS broker. Hermes was the debug tool and Oracle XE was the persistent store for ActiveMQ. I found this combination to be *fairly* painless to setup (barring some installation difficulties with Oracle XE) and very easy to use. The one thing i found difficult to grasp at first was how Weblogic tiered its JMS responsibilities.

Weblogic comes packaged with its own JMS provider, Weblogic JMS. It has a neat configuration interface on the weblogic console where you can setup different "JMS Servers" and "JMS Modules". I found this resource to be particularly useful in understanding the Weblogic JMS setup:
http://download.oracle.com/docs/cd/E12840_01/wls/docs103/jms/fund.html

Now the fact that Weblogic has its own provider doesn't preclude the use of other providers, ActiveMQ for example. The ActiveMQ provider can be configured either via a JEE connector, or deployed as a web application in weblogic (the option i preferred). The provider will automatically bind a management context for JMX over RMI and a broker URL to a TCP endpoint. Any JNDI binding specified in your classpath or programmatically can be looked up using the initial context factory provided: org.apache.activemq.jndi.ActiveMQInitialContextFactory to easily access sconnectionFactory, queueConnectionFactory or topicConnectionFactory.

Some quick examples to illustrate mechanics.

Programatic JNDI setup - Provider URL is the default ActiveMQ broker endpoint:

jndi.properties (classpath):






Once you have setup your destination either in your jndi.properties (classpath) or programatically, or through your application server JNDI management interface - you can use the code example below to send messages to that destination. Notice that the code below uses the JMS API interfaces only, there is no vendor (activeMQ) specific code:
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
 
/**
 * A simple polymorphic JMS producer which can work with Queues or Topics which
 * uses JNDI to lookup the JMS connection factory and destination
 * 
 * @version $Revision: 1.2 $
 */
public final class SimpleProducer {
 
    private static final Log LOG = LogFactory.getLog(SimpleProducer.class);
 
    private SimpleProducer() {
    }
 
    /**
     * @param args the destination name to send to and optionally, the number of
     *                messages to send
     */
    public static void main(String[] args) {
        Context jndiContext = null;
        ConnectionFactory connectionFactory = null;
        Connection connection = null;
        Session session = null;
        Destination destination = null;
        MessageProducer producer = null;
        String destinationName = null;
        final int numMsgs;
 
        if ((args.length < 1) || (args.length > 2)) {
            LOG.info("Usage: java SimpleProducer <destination-name> [<number-of-messages>]");
            System.exit(1);
        }
        destinationName = args[0];
        LOG.info("Destination name is " + destinationName);
        if (args.length == 2) {
            numMsgs = (new Integer(args[1])).intValue();
        } else {
            numMsgs = 1;
        }
 
        /*
         * Create a JNDI API InitialContext object
         */
        try {
            jndiContext = new InitialContext();
        } catch (NamingException e) {
            LOG.info("Could not create JNDI API context: " + e.toString());
            System.exit(1);
        }
 
        /*
         * Look up connection factory and destination.
         */
        try {
            connectionFactory = (ConnectionFactory)jndiContext.lookup("ConnectionFactory");
            destination = (Destination)jndiContext.lookup(destinationName);
        } catch (NamingException e) {
            LOG.info("JNDI API lookup failed: " + e);
            System.exit(1);
        }
 
        /*
         * Create connection. Create session from connection; false means
         * session is not transacted. Create sender and text message. Send
         * messages, varying text slightly. Send end-of-messages message.
         * Finally, close connection.
         */
        try {
            connection = connectionFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            producer = session.createProducer(destination);
            TextMessage message = session.createTextMessage();
            for (int i = 0; i < numMsgs; i++) {
                message.setText("This is message " + (i + 1));
                LOG.info("Sending message: " + message.getText());
                producer.send(message);
            }
 
            /*
             * Send a non-text control message indicating end of messages.
             */
            producer.send(session.createMessage());
        } catch (JMSException e) {
            LOG.info("Exception occurred: " + e);
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                }
            }
        }
    }
}



 Hopefully this leaves you with a slightly better understanding of why we use messaging and uncovers a small bit of insight into the how. Happy coding!