Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Musings on service locators in node?
After working with a lot of node modules I'm a bit angered by the fact that for a lot of these packages you have to pass around and keep track of a single shared instance throughout your application.
This works fine for small proxy servers and apps where the whole thing fits into a single `app.js` or maybe a handful of route files.
But as things start to grow this model really breaks down and becomes cumbersome.
Just think about a resource file for `app/resources/users.js` which needs the current Redis or RabbitMQ connection to publish events like user registration, a database connection to persist things, and probably a Socket.io instance because it's Real Time: it's so hot right now!
If you export a function that allows these to be injected, your resource has boilerplate.
var routeGroup = require('express').Router();
module.exports = function (queue, db, io, somethingElse) {
// Your routes code for this resource goes here
return routeGroup;
};
When we go up to our `app.js` which registers this with our Express application (or whatever micro routing library is cool this week), things are pretty ugly here too!
// app is the express instance
app.use('users', require('./app/resources/users')(queue, db, io, somethingElse));
app.use('orders', require('./app/resources/orders')(queue, db, io, somethingElse));
app.use('cart', require('./app/resources/cart')(queue, db, io, somethingElse));
// Do you get the point...?
Sure if all of our resource files used the same requirements we could write a function and make our app.js look more like this:
function registerResource (name) {
var reqName `./app/resources/{$name}`;
app.use(name, require(reqName)(queue, db, io, somethingElse));
}
But to me this is pretty fragile and this will likely lead to injecting unused services into ALOT of your resources.
---
Instead, what if we could do something like this in our resources:
var routeGroup = require('express').Router();
var db = require('kenx');
var queue = require('redis');
var io = require('io');
// Code goes here and uses db, queue, io just as before when exporting a function to inject!
module.exports = routeGroup;
---
Why do I ask for something like this?
Well Mongoose uses this REALLY well.
For instance I can `require('mongoose')` just about ANYWHERE after after setting up the default connection.
Then I can run `mongoose.model()` and grab a model for the default connection.
Of course if I want a new mongoose instance I could make a new one.
---
## Being realistic
Getting this behavior would put a fairly heavy burden on the authors of EVERY lib that we would want to use in our app.
Instead we could probably make some sort of REALLY simple service locator.
I'm thinking that maybe we could build something out with the new ES2015 Proxies?
Maybe the API could be something like:
var locator = require('proxy-locator');
var mongoose = locator.require('mongoose');
var redis = locator.require('redis');
Then maybe having something like:
locator.register('redis', option1, option2, option3);
When using `locator.require` if the actual module already has a method or property then it would proxy over to the regular module.
If not then the named property/function will be called on the instance that has been registered.
It seems like this is something that could be accomplished with the new ES6 Proxies but I'm a bit too tired and frazzled to figure this out at this point.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment