Communication Between Microservices
This blog post delves into the intricate communication strategies of microservices within an application. Each microservice, though individually simplistic and limited in scope, must interact seamlessly with others to construct the sophisticated functionalities required by the application. The essence of this blog post is to explore the mechanisms through which microservices can effectively communicate, ensuring they can synchronize their operations and collectively achieve the application's objectives. We'll cover the following key areas:
- Implementing application-level live reload for enhanced development speed.
- Facilitating direct communication between microservices using HTTP requests.
- Employing RabbitMQ for indirect communication amongst microservices.
- Strategies for choosing between direct and indirect communication methods.
Throughout this discussion, we'll revisit technologies such as Docker and Docker Compose, particularly focusing on setting up live reload across the entire application. This is crucial for avoiding the need for constant rebuilding and restarting of the application with every code modification. The blog post will expand on the use of HTTP requests, introduced in previous discussions, as a means for direct messaging, alongside introducing RabbitMQ for indirect messaging. By the end of this blog post, you should have a clear understanding of how to select the most appropriate communication method for different scenarios within a microservices architecture.
In this blog post, we delve into the introduction of RabbitMQ, a powerful message-queuing software, which plays a pivotal role in enhancing the decoupling of microservices. To bridge our microservices with RabbitMQ, we will leverage the npm package, amqplib, facilitating the sending and receiving of messages. Additionally, we'll revisit some well-known tools, diving deeper into the utilization of HTTP requests for message transmission and refining our development setup to incorporate live reload across the entire application. This approach aims to streamline our development process, allowing for more efficient iterations and updates.
Getting Our Microservices Talking
As we progress through the blog series, our application currently features two microservices: one for video streaming and another for video storage. In the last chapter, we enhanced our application with data storage functionalities; the video streaming microservice now utilizes a database, while the video storage microservice employs external cloud storage for managing video files.
The essence of a microservices-based application lies in the collaboration of its services to deliver the application's functionalities. Without effective communication between these microservices, the application would fail to perform its intended tasks. Hence, establishing robust communication channels between microservices is fundamental to the microservices architecture.
Indeed, communication has been a critical component from the outset. For instance, the interaction between our video streaming and video storage microservices in the previous blog post, facilitated through HTTP requests, was a crucial step. Although briefly mentioned, this communication method played a pivotal role in segregating the streaming and storage aspects of our application.
Looking ahead, we aim to further evolve our application's architecture by the end of this blog post. While a conceptual illustration provides a glimpse into the future setup, it doesn't delve into the technical specifics we plan to introduce. To fully grasp the upcoming changes, it's important to understand the various communication styles available to us and the underlying technologies that support them. But before we dive into these technicalities, let's take a moment to reflect on the historical context of microservices.
Introducting the History Microservice
In this blog post, we introduce a new microservice known as the "history" microservice to exemplify the dynamic message exchange between microservices within our application, RetailloopTube. As its name implies, this microservice is tasked with logging the viewing history of our users, serving a critical function within the application's ecosystem.
The integration of a history microservice opens up a variety of applications. Users can revisit their viewing history to locate previously watched videos or pick up where they left off. Additionally, this historical data can be instrumental in generating personalized recommendations, enhancing the user experience on RetailloopTube.
To simplify our examples for this chapter, we'll temporarily remove the video-storage microservice introduced in the previous chapter. This step allows us to focus on the video-streaming microservice, which we'll revert to a version used after the third blog post, featuring an example video embedded within its Docker image. This simplification is purely for educational purposes, aimed at easing the understanding of communication techniques. We plan to reintroduce the video-storage microservice and restore the video-streaming microservice to its advanced state in subsequent blog post.
The primary form of communication we'll explore involves the "viewed" message, signifying that a user has watched a video. This message is sent from the video-streaming microservice to the history microservice, which then logs this information in its database.
While we have yet to delve into the specific messaging styles available for this purpose, it's important to note that several options exist for transmitting the "viewed" message. As we progress through this blog post, we'll evaluate these messaging techniques to determine the most appropriate choice for our application's needs. Before we explore these communication strategies further, we'll first focus on enhancing our development environment to facilitate quicker development cycles.
Live reload for fast iterations
The concept of live reload is crucial for enhancing development efficiency, as discussed in the second blog post, emphasizing the significance of rapid, incremental updates for a swift development cycle and a tight feedback loop. Initially, when running our first microservice under Node.js, we utilized the npm package, nodemon, to achieve automatic reloading of the microservice upon code modifications. This automatic reload feature is pivotal, not just at the microservice level but even more so at the application level, where the compilation and startup time for the entire application considerably surpass that of individual microservices.
In our journey, by the time we reached the third blog post, we started incorporating Docker to encapsulate the microservice code within Docker images. Docker serves as a comprehensive solution for packaging, publishing, and deploying our microservices, marking its importance in our development and deployment workflow, despite the deployment aspects yet to be explored fully until the sixth blog post and seventh blog post, where we will witness the deployment of our Docker images in a production setting.
The introduction of Docker Compose in fourth blog post further streamlined our development process, offering a structured and manageable approach to handling the expanding complexity of our application. However, this transition from a direct Node.js environment to Docker containers came with its own set of challenges, notably the loss of the live reload capability. This is because, with our code now "baked" into Docker images, subsequent modifications to the code necessitate rebuilding the images and restarting the application—a process that becomes increasingly cumbersome and time-consuming as our application grows.
This bottleneck in development speed highlights the necessity for a live reload mechanism that functions at the application level, allowing for seamless code updates without the need for constant image rebuilds and application restarts, thus maintaining a fast and efficient development pace.
Creating a stub for the history microservice
const express = require("express");
function setupHandlers(app) {
// stub for handling history microservice
}
function startHttpServer() {
return new Promise(resolve => {
const app = express();
setupHandlers(app);
const port = process.env.PORT && parseInt(process.env.PORT) || 3000;
app.listen(port, () => {
resolve();
});
});
}
function main() {
console.log("Hello world!");
return startHttpServer();
}
main()
.then(() => console.log("Microservice online."))
.catch(err => {
console.error("Microservice failed to start.");
console.error(err && err.stack || err);
});
Updating the Docker Compose file for live reload
To implement an effective application-wide live reload system, an essential step involves configuring our Docker Compose setup to facilitate code and npm cache sharing between the host operating system and the Docker containers. This section will guide you through leveraging Docker volumes for this purpose. Docker volumes are powerful tools that allow for the synchronization of the filesystem between your development workstation and the Docker container. By setting up volumes in our Docker Compose file, any changes made to the code in an Integrated Development Environment (IDE) like Visual Studio Code will be immediately reflected in the microservice that's running as part of our application within Docker Compose. This setup significantly streamlines the development process, providing instant feedback on code modifications without the need for manual rebuilds or restarts, thus maintaining the agility and efficiency of the development cycle.
version: '3'
services:
#.... other services ...
history:
image: history
build:
context: ./history
dockerfile: Dockerfile-dev
container_name: history
volumes:
- /tmp/history/npm-cache:/root/.npm:z
- ./history/src:/usr/src/app/src:z
ports:
- "4002:80"
environment:
- PORT=80
- NODE_ENV=development
restart: "no"
In the updated configuration presented in the code listing above, we introduce a significant change by utilizing Dockerfile-dev, which is tailored for development scenarios. This decision builds upon our discussion from the fourth blog post, where we initially departed from the default Dockerfile naming convention to specify it explicitly. Now, we're specifying Dockerfile-dev to leverage a version of our Dockerfile optimized for development purposes.
Furthermore, we've incorporated the volumes field into our setup, establishing Docker volumes that bridge the gap between the filesystem on our development workstation and that within the container. This connection directly integrates our source code into the container's environment, circumventing the need to hard-code the application directly into the Docker image.
This setup utilizes one Docker volume to sync the source code, while a separate volume is dedicated to sharing a directory for the npm cache. The latter ensures that npm packages installed within the container are cached on the host system. This caching mechanism is incredibly beneficial, as it preserves the npm cache even if the container is destroyed and recreated, streamlining subsequent installations and development cycles.
The `z` flag you encountered in the volume configuration serves a specific purpose in Docker, signifying that the mounted volume should be shared. This is particularly useful when you're working with multiple containers that need access to the same filesystem resources. Essentially, it tells Docker to adjust the SELinux label of the host file or directory being mounted into the container, making it accessible for sharing. This label modification is crucial for maintaining the necessary security protocols while allowing for the efficient sharing of data across containers. This functionality embodies Docker's capability to facilitate collaborative processes between various parts of an application, ensuring that resources like files and directories can be used by multiple containers without compromising on security.
Methods for communication for microservices
Following our detour to enhance our development environment with application-wide live reload capabilities, we pivot back to the core theme of this blog post: the exploration of communication strategies among microservices. Before delving into specific technologies, it's crucial to understand the two fundamental styles of communication in the realm of microservices: direct and indirect messaging.
I prefer the terminology of "direct" and "indirect" messaging over "synchronous" and "asynchronous" to avoid confusion with the traditional computer programming contexts of these terms. Especially since the concept of asynchronous programming can be intimidating for many, we'll steer clear of using the term "asynchronous" in this context.
Direct Messaging
Direct messaging involves one microservice sending a message directly to another, expecting an immediate response. This method is ideal for scenarios where instant action or feedback is required from the recipient microservice. It's akin to issuing direct commands or instructions, expecting prompt execution or feedback on success or failure. The nature of direct messaging means the recipient cannot ignore the message without the sender becoming aware, as illustrated when a video-streaming service sends a "viewed" message to a history service, which then responds directly.
While direct messaging is essential for certain scenarios, it does introduce a high degree of coupling between the involved microservices, which is generally undesirable in a microservices architecture.
Indirect Messaging
Indirect messaging, on the other hand, employs an intermediary to facilitate communication between microservices. This approach allows for a much looser coupling, as the communicating parties do not need to be aware of each other's existence. Messages are passed through an intermediary, and as such, the sender may not even know if the message will be received. Consequently, direct responses to confirm actions are not possible with indirect messaging.
This style is suited for situations where the outcome of the message does not require immediate confirmation. It's particularly useful for broadcasting notifications or events that are of interest to multiple parts of the application without necessitating a direct response. Indirect messaging supports a flexible and decoupled communication structure but does introduce additional complexity. As the application scales, tracing the flow of communication can become challenging due to the lack of direct pathways.
With an understanding of both direct and indirect messaging, we're now equipped to explore the practical implementation of these communication methods in microservices architecture.
Direct Messaging with HTTP
In the preceding blog post, we utilized HTTP primarily for fetching streaming video content from storage. This blog post shifts our focus towards leveraging HTTP to facilitate direct messaging between microservices.
Understanding the Role of HTTP in Direct Messaging:
HTTP requests, especially POST requests, are pivotal for direct messaging between microservices, allowing us to immediately ascertain the success or failure of message handling. This blog post will specifically explore how to employ HTTP POST requests to enable direct communication from the video-streaming microservice to the history microservice.
Why Opt for HTTP?
HTTP stands as the cornerstone of the World Wide Web, making it the default choice for crafting web services. Its universal adoption for creating Representational State Transfer (REST) APIs, coupled with broad support across programming languages and the wealth of available learning resources, underscores its suitability for this purpose. HTTP's omnipresence ensures that we can rely on it for efficient and straightforward communication between microservices.
Locating Microservices via DNS:
Before initiating communication, identifying the target microservice is crucial. The Domain Name System (DNS) complements HTTP by simplifying the process of directing messages to microservices by their names. DNS seamlessly translates hostnames into IP addresses, facilitating effortless message routing within both Docker Compose environments and production Kubernetes clusters. This translation process ensures that messages reach their intended microservices, whether they reside on a development workstation or within a production environment.
Sending Messages with HTTP POST:
Messaging involves a sender and a receiver. To illustrate sending a message, we delve into utilizing an HTTP POST request. This method was briefly touched upon in an earlier section, where we used Node.js's built-in http library for message forwarding. We'll revisit this library to demonstrate message transmission between microservices.
An excerpt from an updated index.js file in the video-streaming microservice showcases the implementation of the `sendViewedMessage` function. This function exemplifies how to send a message via HTTP POST, establishing direct communication between the video-streaming and history microservices.
function sendViewedMessage(videoPath) {
const postOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
};
const requestBody = {
videoPath: videoPath
};
const req = http.request(
"http://history/viewed",
postOptions
);
req.on("close", () => {
…
});
req.on("error", (err) => {
…
});
req.write(JSON.stringify(requestBody));
req.end();
}
To send a direct message via an HTTP POST request, we employ the `http.request` function, targeting the history microservice at the URL `http://history/viewed\`. This URL specifies both the destination microservice ('history') and the specific action or route ('viewed') to be taken. This method allows us to clearly identify where and what our message should accomplish.
The process involves setting up various options for the HTTP request, including the method (POST), the content type of the request body, and the body itself, which contains the message payload. This payload is the crucial information we wish to transmit to the history microservice.
Upon configuring these settings, we send out the HTTP request to its intended target. The process includes mechanisms for handling both the successful transmission and any potential errors that may occur. Callback functions are designated for these purposes, enabling us to respond appropriately to the outcomes of our request—whether that means taking corrective actions in the event of a failure or proceeding with subsequent steps following a success.
This methodical approach to direct messaging via HTTP POST underlines the process of identifying the recipient microservice, crafting the message, and managing the communication's outcome efficiently.
Receiving a message with HTTP POST
On the receiving end, handling HTTP POST messages involves setting up a route handler within the target microservice, often using Express, a popular web application framework. An example of this can be seen in the setup for the history microservice, where a specific route is designated to capture and process incoming POST requests.
In the updated configuration, an HTTP POST route handler for the 'viewed' endpoint is introduced. This handler's primary responsibility is to process incoming messages, which, in this context, contain data about videos that users have watched. The core functionality demonstrated involves taking these messages and storing their contents in the database, effectively maintaining a comprehensive record of user viewing history.
This approach exemplifies how a microservice can listen for and respond to direct messages, capturing essential data from the communication and utilizing it to fulfill a specific part of its functionality, such as tracking and recording user interactions.
function setupHandlers(app, db) {
const videosCollection = db.collection("videos");
app.post("/viewed", (req, res) => {
const videoPath = req.body.videoPath;
videosCollection.insertOne({ videoPath: videoPath })
.then(() => {
console.log(`Added video ${videoPath} to history.`);
res.sendStatus(200);
})
.catch(err => {
console.error(`Error adding video ${videoPath} to history.`);
console.error(err && err.stack || err);
res.sendStatus(500);
});
});
}
Direct messaging enables a structured approach where a central controller microservice can manage and sequence complex interactions among various other microservices. This capability is particularly useful because each direct message elicits an immediate response, allowing the orchestrating microservice to effectively oversee and direct the actions of several others.
This communication model is often referred to as synchronous communication due to the orchestrator's ability to systematically align and sequence messages, ensuring orderly execution of tasks across the microservice architecture. An illustrative example might depict Microservice A as the orchestrator, coordinating the functions of other services in a precise sequence.
Direct messaging shines in scenarios requiring clear, orderly coordination of microservice actions, providing straightforward pathways to trace and understand the sequence of operations. However, while direct messaging offers clarity in the flow of communication, managing the sequence of indirect messages introduces a level of complexity, as we'll explore further.
Indirect Messaging with RabbitMQ
Having explored how to utilize HTTP POST requests for direct messaging, we now shift our focus to indirect messaging. This approach significantly enhances the decoupling of our microservices. While it may introduce a layer of complexity to our application's architecture, the benefits it offers in terms of security, scalability, extensibility, reliability, and performance are substantial.
RabbitMQ plays a crucial role in this context by facilitating the separation of message senders and receivers. In an indirect messaging setup, the entity sending a message does not need to be aware of the specific microservices that will process the message, if any. This level of decoupling allows for a more flexible, resilient, and scalable microservices architecture.
Why RabbitMQ?
RabbitMQ stands out as a premier choice for implementing indirect messaging within microservices architectures. Its reputation as a robust and widely-adopted message queuing software is well-earned, thanks to its extensive use across various industries. RabbitMQ's stability and maturity are notable; it has been a reliable backbone for messaging operations for over a decade. One of its key features is the support for the Advanced Message Queuing Protocol (AMQP), an open standard that facilitates efficient message broker communication.
RabbitMQ's architecture allows a microservice, like the video streaming service, to publish messages to specific queues, such as a "viewed" queue, which can then be accessed by services like the history microservice. This setup not only enables decoupled communication between services but also supports complex messaging patterns and architectures.
The widespread adoption of RabbitMQ is partly due to its comprehensive library support across numerous programming languages, ensuring seamless integration regardless of your technology stack. For Node.js environments, the amqplib library is commonly used, available through the npm registry. Being open-source, RabbitMQ is accessible and relatively straightforward to implement, providing a solid foundation for building flexible and scalable messaging solutions within microservices ecosystems.
Creating a RabbitMQ Server
version: '3'
services:
#....other services defined here...#
rabbit:
image: rabbitmq:3.8.1-management
container_name: rabbit
ports:
- "5672:5672"
- "15672:15672"
expose:
- "5672"
- "15672"
restart: always
# ... more services defined here ...
Connecting our microservice to message queue
Once the RabbitMQ server is operational within our infrastructure, the next step involves integrating our microservices with it. For developers building their setup from the ground up, the initial task is to incorporate the amqplib package into any microservice that requires a connection to RabbitMQ. This is done by executing the command:
npm install --save amqplib
For those working directly with the provided code samples, such as the previous example, it's necessary to ensure all dependencies are installed with:
npm install
The RabbitMQ management dashboard offers a valuable tool for monitoring and debugging the flow of messages across your application. It allows developers to visually inspect message queues, including those dedicated to specific events or interactions, like "viewed" messages in our case.
An upcoming code snippet from the index.js file of the history microservice will illustrate the process of establishing a connection to the RabbitMQ server. This connection is vital for the microservice to send or receive messages, leveraging RabbitMQ's capabilities to facilitate indirect messaging within the application.
// ... other package imports here ...
const amqp = require("amqplib");
const RABBIT = process.env.RABBIT;
// ... code omitted here ...
function connectRabbit() {
return amqp.connect(RABBIT)
.then(messagingConnection => {
return messagingConnection
➥ .createChannel();
});
}
// ... code omitted here ...
function main() {
return connectDb()
.then(db => {
return connectRabbit()
.then(messageChannel => {
return startHttpServer(db,
➥ messageChannel);
});
});
}
main()
.then(() => console.log("Microservice online."))
.catch(err => {
console.error("Microservice failed to start.");
console.error(err && err.stack || err);
});
version: '3'
services:
# ... other services defined here ...
history:
image: history
build:
context: ./history
dockerfile: Dockerfile-dev
container_name: history
volumes:
- /tmp/history/npm-cache:/root/.npm:z
- ./history/src:/usr/src/app/src:z
ports:
- "4002:80"
environment:
- PORT=80
- RABBIT=amqp://guest:guest@rabbit:5672
- DBHOST=mongodb://db:27017
- DBNAME=history
- NODE_ENV=development
depends_on:
- db
- rabbit
restart: "no"
Integrating RabbitMQ into our application introduces a new challenge due to its relatively slow startup time compared to the rapid readiness of our lightweight microservices. This discrepancy can lead to issues if a microservice tries to connect to RabbitMQ before it's fully operational, resulting in errors and potential service disruptions. Addressing this requires managing startup dependencies carefully to ensure that services attempt connections only when RabbitMQ is ready to accept them.
Moreover, for our system to be resilient, it's crucial for microservices to handle potential RabbitMQ downtimes gracefully, such as during upgrades, by automatically reattempting connections when RabbitMQ becomes available again. While implementing a robust solution to this problem involves more complexity, a simpler interim measure can help us avoid immediate startup connection issues.
As a temporary fix, we can modify our Dockerfile to include a delay for our microservice's startup, ensuring it waits until RabbitMQ is ready to establish a connection. One effective tool for this purpose is the `wait-port` command, which can be added to our project with the following npm command:
npm install --save wait-port
This solution provides a straightforward way to manage the timing of service startups, ensuring smoother initial connections. In a later chapter, we'll explore more advanced strategies for managing service dependencies and connection retries in a fault-tolerant manner.
Let’s update the docker file -development file used previously to use the wait-port command
FROM node:21.2.0-alpine
WORKDIR /usr/src/app
COPY package*.json ./
CMD npm config set cache-min 9999999 && \
npm install && \
npx wait-port rabbit:5672 && \
npm run start:dev
Employing `wait-port` is a practical initial step to address the startup synchronization between our microservices and the RabbitMQ server. However, this approach is somewhat basic and doesn't fully encapsulate the resilience required for microservices architecture. Microservices need to be designed with fault tolerance in mind, capable of withstanding downtimes of dependent services and servers. We'll delve deeper into strategies for enhancing fault tolerance in subsequent chapters.
The question might arise as to why the startup order issue didn't manifest in previous blog post with the introduction of MongoDB, which, like RabbitMQ, requires time to become operational. The absence of such problems in the MongoDB scenario can be attributed to the robust engineering behind the MongoDB client library. It includes built-in mechanisms for automatic reconnections, effectively handling the database's readiness without necessitating explicit waits in our service startup process. This exemplifies the value of thoughtful software engineering, where anticipating and addressing the needs and challenges faced by users can significantly enhance the usability and resilience of technology solutions.
RabbitMQ's versatile configuration options offer numerous possibilities for setting up messaging architectures. In this discussion, we're narrowing our focus to two straightforward setups that are particularly effective for a range of common communication scenarios in application development.
Single-Recipient Indirect Messaging
Our first configuration is tailored for scenarios where messages are intended for a single recipient, creating a one-to-one communication channel between microservices in an indirect manner. Despite potentially having multiple senders and receivers within the system, this setup ensures that each message is received and processed by only one microservice. This approach is ideal for distributing tasks among a pool of services, where the task should be executed by only the first service capable of handling it.
Implementing Single-Recipient Messaging in the History Microservice
To enable the history microservice to handle single-recipient messages, it's necessary first to establish a connection with our RabbitMQ server, a step we've previously covered. Following this, the microservice asserts the existence of a message queue, a process that might sound like "creating" a queue but is distinct in practice. When a microservice "asserts" a queue, it checks if the queue already exists, creating it only if it does not. This ensures the queue is shared among all participating microservices, avoiding duplicate creations.
An excerpt from the history microservice's `index.js` file demonstrates how to assert the "viewed" queue and begin receiving messages. Utilizing the `consume` method, the service is set up to trigger the `consumeViewedMessage` function whenever a new message arrives. This setup is relatively straightforward, requiring minimal code to enable the reception of messages via RabbitMQ, showcasing the efficiency of RabbitMQ in facilitating indirect, single-recipient messaging within microservices architectures.
function setupHandlers(app, db, messageChannel) {
const videosCollection = db.collection("videos");
function consumeViewedMessage(msg) {
const parsedMsg = JSON
➥ .parse(msg.content.toString());
return videosCollection.insertOne({ videoPath: parsedMsg.videoPath })
.then(() => {
messageChannel.ack(msg);
});
};
return messageChannel.assertQueue("viewed", {})
.then(() => {
return messageChannel.consume("viewed",
➥ consumeViewedMessage);
});
}
// ... code omitted here ...
Sending Single-Recipient Messages
// ... code omitted here ...
function sendViewedMessage(messageChannel,
➥ videoPath) {
const msg = { videoPath: videoPath };
const jsonMsg = JSON.stringify(msg);
messageChannel.publish("", "viewed",
➥ Buffer.from(jsonMsg));
}
// ... code omitted here ...
Multiple-recipient messages
The concept of sending single-recipient messages introduces us to the basic functionality of RabbitMQ and serves as an excellent starting point for understanding indirect messaging. However, a more complex and often more valuable use case involves sending messages that multiple recipients can receive, akin to broadcasting or multiple-recipient messaging. This approach is particularly useful for disseminating notifications about significant events within the application, such as indicating that a video has been viewed, which might be relevant information for several microservices.
In the context of RabbitMQ, achieving a broadcast-style message distribution requires the use of a message exchange. This setup allows a single microservice, like the video-streaming service, to send a message to the "viewed" exchange. The exchange then efficiently routes this message to numerous anonymous queues, ensuring that all interested or subscribing microservices can simultaneously receive and process the message. This one-to-many messaging pattern significantly enhances the application's ability to broadcast important notifications across different parts of the system, fostering a more interconnected and responsive microservices architecture.
Receiving Multiple-Recipient Messages
// ... code omitted here ...
function setupHandlers(app, db, messageChannel) {
const videosCollection = db.collection("videos");
function consumeViewedMessage(msg) {
const parsedMsg = JSON.parse(msg
➥ .content.toString());
return videosCollection.insertOne({ videoPath: parsedMsg
➥ .videoPath })
.then(() => {
messageChannel.ack(msg);
});
};
return messageChannel
➥ .assertExchange("viewed", "fanout")
.then(() => {
return messageChannel
➥ .assertQueue("", { exclusive: true });
})
.then(response => {
const queueName = response.queue;
return messageChannel
➥ .bindQueue(queueName, "viewed", "")
.then(() => {
return messageChannel
➥ .consume(queueName, consumeViewedMessage);
});
});
}
// ... code omitted here ...
Sending Multiple-Receipient Messages
// ... code omitted here ...
function connectRabbit() {
return amqp.connect(RABBIT)
.then(connection => {
console.log("Connected to RabbitMQ.");
return connection.createChannel()
.then(messageChannel => {
return messageChannel.assertExchange(
➥ "viewed", "fanout")
.then(() => {
return messageChannel;
});
});
});
}
function sendViewedMessage(
➥ messageChannel, videoPath) {
const msg = { videoPath: videoPath };
const jsonMsg = JSON.stringify(msg);
messageChannel.publish("viewed", "", Buffer.from(jsonMsg));
}
// ... code omitted here ...
function main() {
return connectRabbit()
.then(messageChannel => {
return startHttpServer(messageChannel);
});
}
// ... code omitted here ...
Conclusion
If you don’t want to wait for the whole series to end to get the knowledge, you can buy my ebook on gumroad => Microservices . To encourage to keep up the pace with writing this blog post, you can buy me coffee with the button below
Originally published on substack
Enjoyed this?
Subscribe to get new essays in your inbox.