Wednesday, March 25, 2009

Stomp messaging for non-Java programmers on top of Apache ActiveMQ

Recently I was researching available options for messaging between Perl programs. In the past I had quite a lot of experience with Spread and I don't want to repeat. I hated Spread as it was buggy and unstable. So I looked into other alternatives: XMPP, Stomp and AMQP. AMQP has no Perl client so it was out. Stomp and XMPP are closely tied in my view but then Stomp looked simplier so I decided to go with Stomp. There is very good Perl client library for Stomp: Net::Stomp.

Then there is a choose of the server. This is quite an important choice and here is why: Stomp is theoretically language agnostic protocol but in reality you are very likely to depend on semantics of specific Stomp server implementation. For example like I mention below Stomp protocol doesn't really define any rules of message delivery.

There are several servers which support Stomp but Apache ActiveMQ looked to me like one of the most robust implementations. While Apache ActiveMQ supports a wide range of interfaces its design is centered around JMS and it helps to understand basic concepts of JMS even if you use Stomp only. This was a problem for me and as I don't really program in Java and all JMS concepts were alien to me. Moreover most of documentation on Stomp and ActiveMQ takes for granted that you know JMS basics.

So I'm recording all my finding on Stomp/ActiveMQ from viewpoint of non-Java programmer. I hope it might be helpful for other non-Java programmers. Word of warning: all below might be specific to Apache ActiveMQ implementation of Stomp server. I didn't bother to check other Stomp servers.

Basic model

As I mentioned earlier Stomp protocol by itself doesn't specify rules of message delivery. It is up to Stomp server to define the rules. This is where JMS API model becomes important as Stomp implementation is basically just a mapping of JMS model to non-Java specific protocol. Below is the short summary of API model which is relevant to Stomp clients (this is mostly based on my reading of JMS tutorial, Stomp protocol description and description of JMS extensions to Stomp).

There are two distinct ways to organize messaging:

  1. Use queues. If one message gets into queue, only one of subscribers gets it. If there are no subscribers then server stores the message until someone shows up.

  2. Use topics. For each message sent to the topic all active (i.e. connected) subscribers get a copy of it. Actually non-active subscribers can get a copy as well if they register their subscription as durable in advance. If there are no subscribers message gets lost.

How do use queues and topics in Stomp client? It is all controlled by destination you specify when subscribing to messages or sending messages. Destinations like /queue/* act as queues. Destinations like /topic/* act as topics.

There is also a concept of temporary queues and topics in JMS. The idea is that they are visible only to connection which creates them so that client might have private queues and topics. I'm not sure if this is exposed to Stomp clients at all. It might be - I haven't researched this as I don't need it in my application.

Control over reliability of messaging

JMS API gives you some control over reliability of messaging and at least some of it is exposed to Stomp layer.

Message acknowledgement: Stomp client on subscription tells if it acknowledges messages automatically or not. Automatic means that messages is considered delivered even if subscriber doesn't actually read it. I guess there are cases when it makes sense but I'd argue that default behavior should be opposite as for most applications it doesn't.

Message persistence: if Stomp server dies it either losses undelivered messages or it rereads them from some permanent storage. Message persistence controls this.

Message priority: in theory JMS provider tries to deliver higher-priority messages before lower-priority. In practice I have no idea - I didn't research how ActiveMQ implements this as it is not important for my application. Anyway this bit is exposed into Stomp protocol as well.

Message expiration: this defines for how long time server keeps undelivered messages.

Transactions: not sure about this one. Both JMS and Stomp support concept of transactions but I'm not sure what is the exact overlap. I might look into this later but for my application transactions are probably not important.

Configuring ActiveMQ as a Stomp server

Latest version (5.2) seems to support Stomp out of box without need for any additional configuration. As a quick test you can run the following program. It is just a copy&paste from Net::Stomp perldoc - I'm adding it here in case they change perldoc later:

# send a message to the queue 'foo'
use Net::Stomp;
my $stomp = Net::Stomp->new( { hostname => 'localhost', port => '61613' } );
$stomp->connect( { login => 'hello', passcode => 'there' } );
$stomp->send( { destination => '/queue/foo', body => 'test message' } );
$stomp->disconnect;

# subscribe to messages from the queue 'foo'
use Net::Stomp;
my $stomp = Net::Stomp->new( { hostname => 'localhost', port => '61613' } );
$stomp->connect( { login => 'hello', passcode => 'there' } );
$stomp->subscribe(
    {   destination             => '/queue/foo',
        'ack'                   => 'client',
        'activemq.prefetchSize' => 1
    }
);
while (1) {
  my $frame = $stomp->receive_frame;
  warn $frame->body; # do something here
  $stomp->ack( { frame => $frame } );
}
$stomp->disconnect;

Default installation doesn't seem to do any authorization so any login/passcode works.