The original JMS spec first arrived in 2001 with JSR 914. At the time several enterprise messaging systems were already widely available however each had their own unique features and mechanisms which required software that wanted to talk on a given messaging bus to be tightly-coupled to the specific messaging system implementation in use.
Given that "tightly-coupled" is something you want to avoid in your enterprise systems, there was a push to abstract away these implementation-specific system aspects from the messaging client code. To address this, the JMS was developed to define a standardized, consistent programming interface that would work across different JMS-provider implementations. The JMS spec, by its abstracted nature, could only define the lowest-common-denominator of messaging constructs so that they could apply to (or be adapted for) the widest number of messaging systems. The JMS therefore defined two core messaging delivery mechanisms (also referred to as destinations): queues and topics.
- Queues are used for point-to-point messaging (1-to-1), where a message sent to a given queue will only be delivered to a single consumer
- Topics on the other hand are used for pub/sub messaging (1-to-many), where a message sent to a topic will be delivered to all subscribers currently listening to that topic.
Beyond the producer/consumer topology differences between the two, there is another key aspect to consider: time dependency.
A message sent to a queue will remain on that queue until a consumer removes it. In other words, a queue consumer can be brought online and all messages that were sent to the queue prior to the consumer coming online will then be delivered to it.
However, as hinted above, by default subscribers need to have an active subscription at the time a message is published in order to receive it. Any messages published when the subscriber is not listening will not be delivered to said subscriber. There is a slight exception to this rule, durable subscriptions, which allow subscribers to receive all messages between the time they subscribe until they unsubscribe, even if the subscriber was offline when a message was sent.
There are practical limitations to durable subscriptions which reduces the number of use-cases where they would be sufficient. One of the biggest constraints is that durable subscriptions don't cover messages that were sent prior to the subscriber creating the durable subscription. For example if the sending system starts broadcasting messages at 7:00am and the receiving system registers a durable listener at 7:30am, all messages produced between 7:00a-7:30a will not be delivered to the subscriber. Although there are some situations where this type of operation is sufficient, the more common requirement is that the receiving system get ALL messages from the sending system, whether the receiving system was alive or not. What is needed is a pub/sub model but with the delivery guarantees that queues provide. This is where ActiveMQ Virtual Destinations can help.
ActiveMQ Virtual Destinations are a way of mapping logical destinations to one or more actual (also called physical) destinations. There are a couple types of virtual destinations available but the one we will focus on is referred to as a Composite Topic. This particular virtual destination will behave as a topic destination for publishers, but we can map that topic to a series of queues (and/or other topics) in order to achieve the particular delivery characteristics that we are shooting for.
Composite Topics are set up in the ActiveMQ broker configuration and allow for flexibility in designing messaging architecture.
Both these aspects are key:
- We want the flexibility to map a single topic to multiple queues, and
- We want to define this setup in the broker's initialization file so that the broker knows to create the physical queues on startup so it can begin dropping messages sent to the topic to them.
This is what it looks like inside the conf/activemq.xml:
With the broker squared away, the only thing left is to add the appropriate code to the producer(s) and consumer(s) programs. Fortunately since the broker is doing the heavy lifting of forwarding messages behind the scenes, we can just set up producer(s) to publish to the topic and each consumer to connect to their assigned queue as you normally would. From the messaging client perspective there is nothing special or exotic that needs to be additionally configured.
Astute readers may have already guessed that there is some trade-offs involved going this route. The most obvious one is that in the way we've configured it, each receiving system would require its own queue, effectively losing the ability for subscribers to dynamically register. In practice this isn't usually much of an issue since a) normally the number of receiving systems is known and limited and b) we could have easily added a physical topic to the mapping so that messages would also be forwarded to a standard topic. In addition, placing messages in queues rather than say durable subscriptions makes the operation much more visible and easy to work with and even promotes receiving systems' ability to implement load-balancing.
Using virtual destinations the producer can enjoy the simplicity of a pub/sub model with the assurance that messages will be waiting for consumers to pull them from the broker when they are up and ready. Though any robust messaging architecture will require more design, planning, and configuration than we've touched on here, virtual destinations are an approach that may greatly simplify some otherwise very difficult use-cases.