A highly opinionated, yet minimal, set of message bus abstractions for NodeJS. It is built on top of RabbitMQ, with rabbot as the primary library for working with RabbitMQ.
The service bus implementation is basic, but includes several of the most common patterns:
- Send / Receive
- Publish / Subscribe
- Request / Response
The items on the left are "producers" as they produce a message for RabbitMQ to route and handle. Items on the right are "consumers" as they consume a message from a queue in RabbitMQ.
Producers and Consumers inherit from a base class of that name, providing common functionality and a means by which all producers / consumers can be extended (see "middleware" below).
It's all NPM. You're going to want the 'rabbot' package with this, so you will need to do two things:
npm install --save rabbot rabbus
Please note that rabbot is explicitly NOT mentioned as a dependency in the Rabbus package.json file for runtime dependencies. This is done with intent, to help prevent library version conflicts.
If you are upgrading Rabbus from a previous version, be sure to read the Upgrade Guide.
There are several demos available to show the basic use of Rabbus. These demos can be found in the /demos folder, including:
- Send / Receive
- Publish / Subscribe
- Request / Response
Be sure to read the demo documentation for information on how to run these demos.
Detailed Rabbus documentation can be found in the docs folder, with a file for each of the major aspects of Rabbus.
Messaging Patterns:
Other Documentation:
Please see the rabbot documentation for information on configuring RabbitMQ connections.
There are three pairs of pattern-based objects that come with Rabbus. Each of them is meant to be used in combination with it's pair. You are encouraged, however, not to use them directly. While this is certainly possible, I find it is more convenient to inherit from these objects at the point where they need to be used. The configuration of each object can then be encapsulated for the intended use, allowing easier testing and maintenance.
There are a few commonalities between all of these object pairs. Most notably, the object that sends a message to RabbitMQ only needs to know about the exchange to which it sends. Conversely, the object that consumes a message from within RabbitMQ needs to know about both the exchange and the queue to which it subscribes.
Rabbus uses a base message producer and consumer object to provide the functionality of all message producers and consumers. This gives a consistent API to all objects, and makes it easy to create your own patterns and extend the Rabbus feature set.
All message consumers receive a common set of parameters when consuming the message:
<obj>.<consumerMethod>(function(message, properties, actions, next){
});
Each of these parameters gives you specific information or callable function to continue processing or completing the process.
For definitions of these parameters, please see the middleware documentation
In addition to the pattern objects, Rabbus also includes a topology object and use of middleware within it's objects. These object types allow much greater freedom and flexibility with Rabbus, when needed.
The Topology object allows you to pre-define and build the needed topology for your RabbitMQ scenario. You can also use existing topology constructs (exchanges, queues and bindings) with a Topology, preventing Rabbus from attempting to re-create them.
See the topology documentation for more information.
RabbitMQ allows you to specify any number of custom headers and other properties, and rabbot allows you to manipulate them as needed.
With Rabbus, you can also pass any arbitrary properties or headers that you wish, when sending a message. This is done in a few ways.
- Middleware - see the documentation below
- A key/value list when sending a message
To use a key/value list, provide an object with keys / values in either the
send
or publish
method of the Sender / Publisher objects (below).
For example:
sender.send(someMessage, {
expiresAfter: 1000,
headers: {
foo: "bar"
}
});
This will send a message with a TTL of 1 second (1000ms), and a header of
foo: bar
.
If you want to specify both a properties object and provide a callback
method to fire after the message is sent, the callback can be specified as an
onComplete
attribute of the properties:
sender.send(someMessage, {
expiresAfter: 1000,
onComplete: function(){
console.log("the message has been sent!");
}
});
If you don't specify an object literal, you can provide a callback function directly as the second parameter.
sender.send(someMessage, function(){
console.log("the message has been sent!");
});
If you need to limit the number of messages being processed by any given
messgae handler, you can specify a limit
in the configuration.
function SomeSubscriber(){
Rabbus.Subscriber.call(this, rabbot, {
// ...
queue: {
// ...
limit: 1
}
});
}
This will limit your SomeSubscriber
to only working on one message at a time.
When your processing code calls done
, the next message will be picked up
and processed.
rabbot's default behavior is to batch process ack
and nack
calls on messages. This can lead to an improvement of up to 400%
throughput in processing small things. In scenarios where there
are very long running processes that leave a message unacknowledged
for extended periods, though, this can be troublesome.
To prevent issues with batching ack / nack calls, rabbot and
Rabbus provide a noBatch
option for Queue definitions.
var Subscriber = new Rabbus.Subscriber({
// ...
queue: {
//...
noBatch: true
}
});
The following Rabbus objects provide the noBatch
feature:
- Rabbus.Receiver
- Rabbus.Subscriber
- Rabbus.Responder
Rabbus is built on top of the rabbot library, which uses a
messageType
attribute for messages. The behavior of the
messageType
attribute makes rabbot and Rabbus behave
somewhat differently than RabbitMQ / AMQP on their own.
Internally, rabbot uses an in-memory messaging library called
Postal.js to facilitate message delivery to consumer functions.
The messageType
attribute is used by postal as it's own form
of a routing key.
Because of this, you can have two consumers receive a copy of a single message from a single queue... or, have messages from different queues show up in a single subscriber.
c1 = new Rabbus.Subscriber({
// ...
exchange: "foo",
routingKey: "foo",
queue: "foo",
messageType: "foo" // <= note the message type
});
c2 = new Rabbus.Subscriber({
// ...
exchange: "bar",
routingKey: "bar",
queue: "bar",
messageType: "foo" // <= same message type
});
c1.subscribe(function(msg, props, actions){
console.log("c1 got it!");
actions.ack();
});
c2.subscribe(function(msg, props, actions){
console.log("c2 got it!");
actions.ack();
});
In this example, it is highly likely that you will receive both
a "c1 got it!" message and a "c2 got it!" message in the
console, when publishing a message for c1 to consumer. This
happens because of the messageType
being the same. rabbot
has internally used the messageType
to say that both the c1
and c2 handler methods should receive the message.
Leaving the messageType
blank will cause rabbot to use the
routing key for the message, as the means by which it delivers
messages to handlers. As long as you are using unique routing
keys, you should probably leave the messageType
blank.
That's not to say there isn't value in what rabbot does. This is just different than standard RabbitMQ/AMQP.
Rabbus is Copyright ©2016 Muted Solutions, LLC. All Rights Reserved.
Rabbus is distributed under the MIT license.