Manage your services —node.js dependency injection

slava hatnuke
3 min readJun 15, 2016

--

Why? Because the dependency injection is the way to independence :)

What does the dependency injection container do? I will try to answer this question.

We are working with a project that has been developed for 3 years and in which about 100 database entities. In addition, Redis, Neo4j, MySQL, caching, search. In general, all as usual complex.

This project uses an approach in which each entity has a Repository.

To put it simply if we have the User model we have UserRepository which stores the business logic.

Schematically, I will show it like this:

User -> UserRepository

This approach is good, because application logic that stored on the controller level looks much worse. All of this can be shown as:

Game -> GameRepository
Card -> CardRepository
Payment -> PaymentRepository
....

So we have 100 repositories. Here is an example:

// UserRepository.js
module.exports = function (app) {
function UserRepository() {
this.User = mongoose.model('User')
}
UserRepository.prototype.getFriends = function (user) {
// business logic here
}
app.set('userRepository', new UserRepository());}

This approach allows us to store application business logic.

Example of initializing such repository:

// app.js
var express = require('express');
var app = express();
require('./User')(app);
require('./UserRepository')(app);
var userRepository = app.get('userRepository');

At this stage, it looks easy, till we do not take into account all the possible variations of initialization the 100 repositories and 100 entities.

I propose to add GameRepository.

// app.js
var express = require('express');
var app = express();
require('./Game')(app);
require('./GameRepository')(app);
require('./User')(app);
require('./UserRepository')(app);
var userRepository = app.get('userRepository');

But if we used UserRepository in GameRepository, we do not have such opportunity to get it, because it hasn’t been created yet.

In this case, everything is simple, we can change the initialization and the issue is solved.

require('./User')(app);
require('./UserRepository')(app);
require('./Game')(app);
require('./GameRepository')(app);
// UserRepository can be used. [SOLVED]

Let us remember that we have a 100 entities + 100 repositories. And when we move the initialization of one of the repositories we can break another repository. It looks like a hell :)

But that’s not all the problems. Let’s imagine that we need PlayerService that uses GameRepository and UserRepository. And GameRepostiory uses UserRepository.

Here is an example of code:

var userRepository = new UserRepository(User);
var gameRepository = new GameRepository(Game, userRepository)
var playerService = new PlayerService(userRepository, gameRepository);playerService.getActivity().then(...) /// just for example

Actually I was only needed PlayerService but because it has dependencies on other I have to worry about creating dependencies.

It was decided to use a dependency injection container.

As developer I would like to take services (repositories) from the container and use it. I would be happy if it looked like this.

container.get('GameRepository').getUserGames(user).then(......)

or

container.get('PlayerService').getActivity().then(...)

And all the work of creating the repository and processing dependencies was on the dependency injection container side.

Let’s look at how the container works.

First imagine a container in which I can register services.

var container = new Container();
container.register([SERVICE_NAME], [SERVICE], [DEPENDENCIES])

In this example, let’s initialize UserRepository

var container = new Container();container
.register('UserRepository', require('./UserRepository');

Here is an UserRepository example

// UserRepository.js
function UserRepository() {}
UserRepository.prototype.getFriends = function (user) {
// business logic here
}
module.exports = UserRepository;

If I ask the container to get repository I want to get it! How it was created and how dependencies have been processed — it is the responsibility of the container.

var repository = container.get('UserRepository');
repository instanceof UserRepository // true

But where is dependency injection? Yes I agree. In this example, no dependencies.

Let’s return back to the services registration. We are creating a container with several services that have dependencies.

var container = new Container();container
.register('User', require('./User'));
container
.register('UserRepository', require('./UserRepository'), ['User']);
container
.register('Game', require('./Game'));
container
.register('GameRepository', require('./GameRepository'),
['Game', 'UserRepository']);
container
.register('PlayerService', require('./PlayerService'),
['UserRepository', 'GameRepository']);

In this example I highlighted the important things. When the service (model, repository, facade) is registered in the container we have ability to specify dependencies which will be provided by container.

We have registered PlayerService which depends on UserRepository and GameRepository.

So if we need PlayerService let’s take it from the container.

var playerService = container.get('PlayerService'); 
playerService.getActivity().then(...) /// just simply

In this case, we don’t care about dependencies because they have already been described in the container and will be initialized in the right order.

Dependency injection container allows us to structure and manage dependencies and services and not the other way around :)

How to use, documentation:

https://www.npmjs.com/package/plus.container

Best regards +1G Team

--

--