node.jsexpressasync-awaitredisioredis

Redis pub/sub for async/await response on Nodejs


I have two NodeJs Express applications linked to two different database, for convenience let's call the first application A and the second B, both are linked to the same redis instance with Ioredis.

A can't access directly B database, it needs to do an HTTP request in order to get data from B database.

In an application A endpoint, I need to check a value from application B database, it is a really high traffic endpoint with hundreds of requests per minutes, I can't simply do an http request to get data from application B, the performance loss will be too high.

A solution that I found is redis Pub/Sub, I can register from two channels in application A and B.

If the required value is not present in redis i'll use two channels :

One channel to send messages from application A channel to application B in order to persist objects from database into redis.

A second channel to send messages from application B to application A in order to persist and return the object that was fetch from database.

My question is, is there a way in the application A first channel call to await a return response from application B through the second channel ?

Can something like this work ?

Basic POC

  async function main() {

    await redis.subscribe("get-company", (err, count) => {
      if (err) console.error(err.message);
    });

  
    redis.on("message", (channel, message) => {
      console.log(`Received first message from ${channel} channel.`);
      redis.publish('send-company', 'test')
      console.log(message);
    });
    main2()
  }

  async function main2() {
    await redis2.subscribe("get-company", (err, count) => {
      if (err) console.error(err.message);
    });

    redis2.publish('get-company', 'test').then(async (data)=>{
      console.log('first send completed');
      redis2.on("message", (channel, message) => {
        console.log(`Received response from ${channel} channel.`);
        console.log(message);
        redis2.unsubscribe();
      })
    })
  }

main()

Solution

  • You can do something like below. Note it is the basic layout. Please update the logic as per your need.

    Fist step would be to divide the two applications for clarity.

    In Application A:

    1. We will subscribe to send-company channel
    2. We will publish a specific message to get-company channel inside a promise
    3. We will wait and resolve the message once the response is received in the send-company channel inside the promise
    // import necessary modules as per your need
    // import necessary modules as per your need
    const Redis = require('ioredis');
    const redisAppASub = new Redis();
    const redisAppAPub = new Redis();
    
    async function appA(){
    
        await redisAppASub.subscribe("send-company", (err, count) => {
            if (err) console.error(err?.message);
        });
    
        redisAppASub.on("message", async (channel, message) => {
            if (channel === "send-company") {
                console.log(message);
    
                // handle the received message here
    
                // unsubscribe if required
                // redisAppA.unsubscribe();
            }
        });
    
        // Possible solution for the query ["My question is, is there a way in the application A first channel call to await a return response from application B through the second channel ?"]
    
        async function requestDataFromAppB(){
            return new Promise((resolve, reject) => {
                redisAppAPub.publish("get-company", "requestDBData", (err) => {
                    if (err) return reject(err);
    
                    console.log("request sent to Application B");
                });
    
                // Temporary subscription to listen and resolve the response
                redisAppASub.once("message", (channel, message) => {
                    if (channel == "send-company"){
                        resolve(message)
                    }
                });
            });
        }
    
        // call the requestDataFromAppB and get the response
        const response = await requestDataFromAppB();        
        console.log(response, " <<--------- response received from Application B");               
    }
    
    // call as per your need
    appA().catch(err => console.error(`Application A error: ${err?.message}`));
    

    In Application B:

    // import necessary modules as per your need
    const Redis = require('ioredis');
    const redisAppBSub = new Redis();
    const redisAppBPub = new Redis();
    
    async function appB(){
        console.log("Application B is starting...");
    
        await redisAppBSub.subscribe("get-company", (err, count) => {
            if (err) console.error(err?.message);
        });
    
        redisAppBSub.on("message", async (channel, message) => {
            if (channel == "get-company" && message == "requestDBData"){
            const dbData = await fetchDataFromDatabase();
            console.log(dbData, ` <<----- dbData`);
    
            redisAppBPub.publish("send-company", dbData, (err) => {
                if (err) console.log(err?.message)
                console.log("response sent to Application A")
            });
            }
        });
    
        async function fetchDataFromDatabase() {
            // Your data fetching logic here
    
            // Simulate fetching data with a delay using a Promise
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(JSON.stringify({data: "data from database"}));
                }, 5000);
            });
        }
    }
    
    // call as per your need
    appB().catch(err => console.error(`Application B error: ${err?.message}`));