visit
However, once you’ve chosen event-driven, you still have several crucial design decisions to make. (See this article on event-driven architectures and options.) And one of the first, and most important, decisions is whether to use message queues or streams.
In message queues, a sender places a message targeted to a recipient into a queue. The message is held in the queue until the recipient retrieves it, at which time the message is deleted.Similarly, in streams, senders place messages into a stream and recipients listen for messages. However, messages in streams are not targeted to a certain recipient, but rather are available to any and all interested recipients. Recipients can even consume multiple messages at the same time, and can play back a series of messages through the streams history.In this article, we’ll narrow our focus to message queues. We’ll create and deploy a simple, and quick to stand up, message queue using , , and . And we’ll look at how our system works, what it can do, and some advantages.Heroku, Redis, and RSMQ—A Great Combination for Event-Driven
, with its one-click deployments and “behind-the-scenes” scaling, and , an in-memory data store and message broker, are an excellent pair for quickly deploying systems that allow us to focus on business logic, not infrastructure. We can quickly and easily provision a Redis deployment (dyno) on Heroku that will scale as needed, and hides the implementation details we don’t want to worry about. is an open-source simple message queue built on top of Redis that is easy to deploy. RSMQ has several nice features: it’s lightweight (just 500 lines of javascript), it’s fast (10,000+ messages per second), and it guarantees delivery of a message to just one recipient. We’ll also follow the “” pattern, which is recommended by Heroku and will give us our desired decoupling and scalability. Using this pattern, we’ll deploy a web client (the browser in the below diagram) that handles the user input and sends requests to the backend, a server (web process) that runs the queue, and a set of workers (background service) that pull messages from the queue and do the actual work. We’ll deploy the client/server as a web dyno, and the worker as a worker dyno.Let’s Get Started
Once you’ve created your Heroku account and installed the Heroku CLI, you can create and deploy the project easily using the CLI. All of the source code needed to run this example .$ git clone //github.com/devspotlight/example-message-queue.git
$ cd example-message-queue
$ heroku create
$ heroku addons:create heroku-redis
$ git push heroku master$ heroku ps:scale worker=1
$ heroku open
System Overview
Our system is made up of three pieces: the client web app, the server, and the worker. Because we are so cleanly decoupled, both the server and worker processes are easy to scale up and down as the need arises.The Client
Our client web app is deployed as part of our web dyno. The UI isn’t really the focus of this article, so we’ve built just a simple page with one link. Clicking the link posts a generic message to the server.
The Web Server
The web server is a simple Express server that delivers the web client. It also creates the queue on startup (if the queue doesn’t already exist), receives new messages from the client, and adds new messages to the queue.Here is the key piece of code that configures the variables for the queue:let rsmq = new RedisSMQ({
host: REDIS_HOST,
port: REDIS_PORT,
ns: NAMESPACE,
password: REDIS_PASSWORD
});
rsmq.createQueue({qname: QUEUENAME}, (err) => {
if (err) {
if (err.name !== "queueExists") {
console.error(err);
return;
} else {
console.log("The queue exists. That's OK.");
}
}
console.log("queue created");
});
app.post('/job', async(req, res) => {
console.log("sending message");
rsmq.sendMessage({
qname: QUEUENAME,
message: `Hello World at ${new Date().toISOString()}`,
delay: 0
}, (err) => {
if (err) {
console.error(err);
return;
}
});
console.log("pushed new message into queue");
});
The Worker
The worker, which fittingly is deployed as a worker dyno, polls the queue for new messages, then pulls those new messages from the queue and processes them.We’ve chosen the simplest option here: The code reads the message, processes it, then manually deletes it from the queue. Note that there are more powerful options available in RSMQ, such as "pop”, which reads and deletes from the queue at the same time, and a “real-time” mode for pub/sub capabilities.rsmq.receiveMessage({ qname: QUEUENAME }, (err, resp) => {
if (err) {
console.error(err);
return;
}
if (resp.id) {
console.log("Hey I got the message you sent me!");
// do lots of processing here
// when we are done we can delete the message from the queue
rsmq.deleteMessage({ qname: QUEUENAME, id: resp.id }, (err) => {
if (err) {
console.error(err);
return;
}
console.log("deleted message with id", resp.id);
});
} else {
console.log("no message in queue");
}
});
If you haven’t already, read the other articles in our series on microservices including best practices for an event-driven architecture and how stream processing makes your event-driven architecture better.