We use Catalyst::Engine::Stomp for all our ActiveMQ consumers. It works rather well, and (especially with the addition of our Net::ActiveMQ) makes working with queues mostly painless.
But there is always room for improvement. There were two main missing pieces:
- generalisation of the subscriptions and inputs, from “a queue per
controller” to “one subscription per controller”
- dispatching to an action based on the JMSType header, instead
of the ad-hoc @type body attribute we have been employing
This is the story of how I implemented both.
Generalising the inputs
The action_namespace attribute of controllers was used to derive the name of the queue to subscribe to, for messages destined for that controller. This works, until you want to subscribe to a topic.
The first step of generalisation is easy: if the namespace starts with topic/, don’t assume it’s a queue. With the addition of some nicer back-compatibility, this is the core of my first commit:
While I was there, I also implemented a feature that was almost-documented: global (and per-broker) subscription headers.
Next, I noticed (mostly by looking harder at the STOMP protocol specification) that the module did not allow the specification of credentials for the connection to the broker. Also, the subscription headers would be much more useful if they were per-controller, not just per-broker.
So I implemented those as well: https://github.com/dakkar/catalyst-engine-stomp/commit/bf4a21663765f92b7690591e0eb07589c2f50306
The documentation I added is probably not my best piece of prose, but I sincerely could not find a clearer way of explaining the details (patches welcome!).
The important consequence is that you can now have:
- credentials at connection
- general subscription options
- different controllers subscribing to the same queue (or topic) with different selectors
They both get messages from the same queue, /queue/newstyle, but the first one only receives messages of type test_foo, or with custom_header: 1, while the second receives test_bar or custom_header: 2. As the test confirms, the routing works as you would expect: TestNewStyleController.pm only gets one set of messages, TestNewStyleController2.pm only gets the other set.
Why would I want to do that??
A couple of examples:
- your architecture is weird and you get disparate messages on the same destination, but you don’t want to rewrite half the dispatcher or have unrelated logic in the same controller
- your subscriptions come from a configuration file, and you want to give your users the freedom to do strange things
Dispatch on header value
The reason we dispatched on @type was that the message headers were not passed to the Catalyst dispatcher by Catalyst::Engine::Stomp. This part was rather easy to solve:
the HTTP::Request used inside Catalyst is similar enough to a STOMP frame that I was able to just shove the STOMP headers into $c->req (and then extract headers from $c->res for the optional reply).
Then, I changed Catalyst::Controller::MessageDriven to (optionally, to keep backward-compatibility) use the header value instead of the message type from the body.
As soon as PMOONEY merges my changes (there are some test failures, apparently, that I can’t reproduce), we’ll be able to use the full flexibility of the STOMP protocol in our Catalyst applications.