Search code examples
node.jsrabbitmqamqpfeathersjs

How to set up a persistent connection for an amqplib wrapper across hooks / services in Feathers app


I'm writing some microservices using Feathers. Pretty new to Feathers (and Node in general) and have run into an issue.

To communicate between the microservices (i.e. a bunch of independent Feathers apps), I'm going to use RabbitMQ (an AMQP message broker). After certain service methods have been called, I'd like to publish a message over AMQP. Similarly, I'd like the application to subscribe to an exchange on the broker and call a service method when a certain message is received.

Since I have about a dozen microservices, I decided to write a library that handles things like enforcing a topic structure and doing reconnects etc. The library is mostly just a wrapper around amqplib. I'm aware that a few open-source feathers amqp modules are floating around but none of them do exactly what I want.

The library has a connect() method, publish() method, and a subscribe() method. The connect() method takes a url, connects to the broker, and stores the connection object in a module variable.

The plan is to register the publish() method as a hook in each of the relevant services. For example, if I had a service called foo, it might have a foo.hooks.js:

module.exports = {
  before: {
    all: [],
    find: [],
    get: [],
    create: [amqpWrapper.publish()],
    update: [amqpWrapper.publish()],
    patch: [amqpWrapper.publish()],
    remove: [amqpWrapper.publish()]
  }
//etc
};

where the publish() method is grabbing the context object, pulling out the info it needs, and chucking it out over amqp.

And a foo.service.js:

// Initializes the `crunch` service on path `/foo`
const createService = require('feathers-nedb');
const createModel = require('../../models/crunch.model');
const hooks = require('./crunch.hooks');

module.exports = function (app) {
  const Model = createModel(app);
  const paginate = app.get('paginate');

  const options = {
    Model,
    paginate
  };

  // Initialize our service with any options it requires
  app.use('/foo', createService(options));

  // Get our initialized service so that we can register hooks
  const service = app.service('foo');

  service.hooks(hooks);
};

I might have a very similar service called bar that also publishes in its hooks.

The issue lies with the fact that you are supposed to maintain only one connection per process to rabbitmq. Opening and closing the connection is slow, so ideally I just want to call the connect() method once when the app starts, and have that persist and be globally accessible from all my different services.

I could of course just do

const amqpWrapper = require('amqpWrapper')

In all of the [service].hooks.js files, as well as in the top level index.js, where I would also call the connectMethod(), but this doesn't seem right to me. Javascript doesn't really have singletons, and while it uses caching for require(), it isn't guaranteed.

So, what is the correct way of importing a module with a persistent connection that is accessible everywhere in Feathers? Can I store it in the 'app' object somehow?

Edit

Followed Daff's suggestion and this works, but just wanted to expand on the full solution:

Since my amqpWrapper() returns the library's module.exports, I save the connection in /src/app.js using app.set('amqp', amqpWrapper.connect()), and then in my foo.hooks.js, I just do:

module.exports = {
  after: {
    all: [],
    find: [],
    get: [],
    create: [context => {context.app.get('amqp').publish();}],
    update: [],
    patch: [],
    remove: []
  },
//etc
};


Solution

  • This is what app.get and app.set can be used for. In src/app.js:

    const amqpWrapper = require('amqpWrapper')
    const connection = amqpWrapper.connect();
    
    app.set('amqp', connection);
    

    Now anywhere you have access to app can use app.get('amqp') to retrieve the connection. In a hook this would be context.app.get('amqp').