Demystifying Dependency Injection, Inversion of Control, Service Containers and Service Providers
Tags: node, javascript, ioc, di
Published: 2019-05-05 22:34:09

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/demystifying-dependency-injection). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.

This article is meant to demystify those scary terms DI and IoC. We are going to code this in a node environment.
Imagine having the following code

```javascript
// index.js

class Database {
    insert(table, attributes) {
        // inserts record in database
        // ...

        const isSuccessful = true
        return isSuccessful
    }
}

class UserService {
    create(user) {
        // do a lot of validation etc.
        // ...

        const db = new Database
        return db.insert('users', user)
    }
}

const userService = new UserService
const result = userService.create({ id: 1})
console.log(result)
```

Running `node index.js` should now log the value "true".

What is happening in the code? There is a Database class used to save things into the database and a UserService class used to create users. The users are going to be saved in the database, so when we create a new user, we new up an instance of Database. In other words, UserService is dependent on Database. Or, Database is a dependency of UserService.

And here comes the problem. What if we were to write tests to check the part `// do a lot of validation etc.`. We need to write a total of 10 tests for various scenarios. In all of these tests, do we really want to insert users into the database? I don't think so. We don't even care about this part of the code. So it would be nice if it was possible to swap out the database with a fake one when running tests.

# Dependency Injection

Enter dependency injection. It sounds very fancy, but in reality is super simple. Rather than newing up the Database instance inside the "create" method, we inject it into the UserService like this.

```javascript
class Database {
    insert(table, attributes) {
        // inserts record in database
        const isSuccessful = true
        return isSuccessful
    }
}

class UserService {
    constructor(db) {
        this.db = db
    }

    create(user) {
        return this.db.insert('users', user)
    }
}

const db = new Database
const userService = new UserService(db)

const result = userService.create({ id: 1})
console.log(result)
```

And the test could look something like this

```javascript

class TestableDatabase {
    insert() {
        return true
    }
}


const db = new TestableDatabase
const userService = new UserService(db)
```

But of course, I hear what you saying. While we made the code testable, the API suffered from it. It's annoying to always pass in an instance of Database.

# Inversion of Control

Enter Inversion of Control. Its job is to resolve dependencies for you.

It looks like this: At the start of the app you bind the instantiation to a key and use that later at any point.

Before we check out the code of our IoC container (also called **service container**), let's look at the usage first.

```javascript
ioc.bind('userService', () => new UserService(new Database))
```

Now you can use ioc.use at any point in your app to access the userService.

```javascript
ioc.use('userService').create({ id: 1})
```

Whenever you call `ioc.use('userService')`, it will create a new instance of UserService, basically executing the callback of the second function. If you prefer to always access the same instance, use app.singleton instead of app.bind.

```javascript
ioc.singleton('userService', () => new UserService(new Database))

ioc.use('userService').create({ id: 1})
```

## Implementation of ioc

```javascript
global.ioc = {
    container: new Map,
    bind(key, callback) {
        this.container.set(key, {callback, singleton: false})
    },
    singleton(key, callback) {
        this.container.set(key, {callback, singleton: true})
    },
    use(key) {
        const item = this.container.get(key)

        if (!item) {
            throw new Error('item not in ioc container')
        }
        
        if (item.singleton && !item.instance) {
            item.instance = item.callback()
        }

        return item.singleton ? item.instance : item.callback()
    },
}
```

That's not a lot of code at all!
so the methods `bind` and `singleton` just store the key and callback inside a map and with the `use` method, we get what we want from the container again.
We also make `ioc` a global variable so it is accessible from anywhere.

But where do we put all those ioc bindings?

## Service Providers

Enter the service provider. Another fancy term simply meaning "This is where we bind our stuff in the service container". This can be as simple as having

```javascript
// providers/AppProvider.js

function register() {
    ioc.singleton('userService', () => new UserService(new Database))
}

module.exports = { register }
```

The register method of the provider is then simply executed at the start of your app.

## Testing

How do we test it now?

Well, in our test we can simply override the userService in the service container.

```javascript

class TestableDatabase {
    create() {
        return true
    }
}


ioc.singleton('userService', () => new UserService(new TestableDatabase))

ioc.use('userService').create({id: 1})
```

This works, but there is the problem that if you have tests that require the actual database in the userService, these might also receive the TeastableDatabase now. Let's create a `fake` and `restore` method on the ioc object instead. We also have to alter our `use` method a little

```javascript
global.ioc = {
    container: new Map,
    fakes: new Map,
    bind(key, callback) {
        this.container.set(key, {callback, singleton: false})
    },
    singleton(key, callback) {
        this.container.set(key, {callback, singleton: true})
    },
    fake(key, callback) {
        const item = this.container.get(key)
        
        if (!item) {
            throw new Error('item not in ioc container')
        }

        this.fakes.set(key, {callback, singleton: item.singleton})
    },
    restore(key) {
        this.fakes.delete(key)
    },
    use(key) {
        let item = this.container.get(key)
        
        if (!item) {
            throw new Error('item not in ioc container')
        }

        if (this.fakes.has(key)) {
            item = this.fakes.get(key)
        }

        if (item.singleton && !item.instance) {
            item.instance = item.callback()
        }

        return item.singleton ? item.instance : item.callback()
    },
}
```

And let's update our test

```javascript

class TestableDatabase {
    insert() {
        return true
    }
}


ioc.fake('userService', () => new UserService(new TestableDatabase))

ioc.use('userService').create({id: 1})

ioc.restore('userService')
```

## Other use cases

### Avoids useless abstractions

This example is taken from [the Adonis documentation](https://adonisjs.com/docs/4.1/ioc-container).

Some objects you want to instantiate one time and then use repeatedly. You usually do this by having a separate file just to handle the singleton.

```javascript
const knex = require('knex')

const connection = knex({
  client: 'mysql',
  connection: {}
})

module.exports = connection
```

With the IoC container this abstraction is not necessary, thus making the code base cleaner.

### Avoids relative require

Imagine you are somewhere very deep inside the file `app/controllers/auth/UserController.js` and want to require the file `app/apis/GitHub.js`. How do you do that normally?

```javascript
const GitHub = require('../../apis/GitHub')
```

How about we add this to the service container instead?

```javascript
// providers/AppProvider.js

ioc.bind('API/GitHub', () => require('../app/apis/GitHub')
```

and now we can use it like this from anywhere

```javascript
ioc.use('API/GitHub')
```

Since it is annoying to do that for every file, let's simply add a method to require files from the root directory.

Add the following code to the end of the `ioc.use` method and remove the exception throw when the key was not found.

```javascript
global.ioc = {
// ...
    use(key) {
        // ...
        return require(path.join(rootPath, namespace))
    }
}
```

Now we can access the GitHub service using

```javascript
ioc.use('apis/GitHub')
```

But with that the ioc container must live in the root of the directory. Let's extract the IoC container out and make a factory out of it. The end result is

```javascript
//lib/ioc.js

module.exports = function createIoC(rootPath) {
    return {
        container: new Map,
        fakes: new Map,
        bind(key, callback) {
            this.container.set(key, {callback, singleton: false})
        },
        singleton(key, callback) {
            this.container.set(key, {callback, singleton: true})
        },
        fake(key, callback) {
            const item = this.container.get(key)
            
            if (!item) {
                throw new Error('item not in ioc container')
            }
    
            this.fakes.set(key, {callback, singleton: item.singleton})
        },
        restore(key) {
            this.fakes.delete(key)
        },
        use(namespace) {
            let item = this.container.get(namespace)
            
            if (item) {
                if (this.fakes.has(namespace)) {
                    item = this.fakes.get(namespace)
                }
        
                if (item.singleton && !item.instance) {
                    item.instance = item.callback()
                }
        
                return item.singleton ? item.instance : item.callback()
            }

            return require(path.join(rootPath, namespace))
        }
    }
}
```

We wrapped the object inside the function `createIoC` that expects the root path to be passed in. The "require" method now returns the following `return require(rootPath + '/' + path)`.

And inside `index.js` we now have to create the container like this

```javascript
global.ioc = require('./lib/ioc')(__dirname)
```

---

And that's it for the basics of IoC! I put the code on GitHub where you can check it out again. I also added some tests to it and made it possible to fake root requires as well.

{% github https://github.com/MZanggl/ioc-node %}

---

If this article helped you, I have a lot more tips on simplifying writing software [here](https://michaelzanggl.gumroad.com/l/intent-driven-development).