DylanBaine.com / Browse / Dependency Inversion: An Example

Dependency Inversion: An Example

The easiest way to make your code is more extensible is by making sure that your high level code and low level code aren’t mixed together. What’s high level and low level code? In this context, low level code would be considered code that interacts with a data store, filesystem, or third party API’s/libraries. High level code is the code that follows business logic. IE: when creating a user, check to see if that user already exists before creating it.

A practical example of dependency inversion outside of software is a flash light that takes AA batteries. The flashlight doesn’t care what brand the battery is, as long as it is a certain shape that provides power through specific points, the flashlight will work.

An Example

So let’s say that you work for an ice cream shop owner named Bob. He needs a web page that will show how much ice cream he has sold, but the page should be behind a login page. To persist Bob’s username and password, you may make a function like the one below.

function createBob() {
    const bcrypt = require("bcrypt")
    const saltRounds = 10
    const mysql      = require('mysql');
    const connection = mysql.createConnection({
        host: 'sql.org',
        user: 'admin',
        password: 'secret'
    });
    connection.query(`SELECT count(*) from users WHERE username = 'bob' LIMIT 1;`, (err, result) => {
        if (err) {
            throw err;
        } else if (result[0].count > 0) {
            throw 'User already exists';
        } else {
            bcrypt
                .genSalt(saltRounds)
            .then(salt => {
                return bcrypt.hash('p4s5w0rd', salt)
            })
            .then(hashedPassword => {
                connection.query(`INSERT into users (username, password) VALUES ('${userName}', '${hashedPassword}');`);
            });
        }
    });
    return { userName: 'bob' }
}

As you can see, this code first checks to see if Bob is already in the database, and if he isn’t, it creates a database row for Bob. This is great and may very well be all that you need, but what if Bob hires an accountant that he wants to give access to the web page? You could create a new method that does the same logic, but with a different username and password, or you could invert the dependency on the username and password, and let them be injected through the function arguments.

A Simple Refactor

Here’s what that function may look like. The first step was obviously to rename the function to be more appropriate for what it does. The second step was to add arguments to the function.

function createUser(userName, password) {
    const bcrypt = require("bcrypt")
    const saltRounds = 10
    const mysql      = require('mysql');
    const connection = mysql.createConnection({
        host: 'sql.org',
        user: 'admin',
        password: 'secret'
    });
    connection.query(`SELECT count(*) from users WHERE username = '${username}' LIMIT 1;`, (err, result) => {
        if (err) {
            console.log(err);
        } else if (result[0].count > 0) {
            throw 'User already exists';
        } else {
            bcrypt
                .genSalt(saltRounds)
            .then(salt => {
                return bcrypt.hash(password, salt)
            })
            .then(hashedPassword => {
                connection.query(`INSERT into users (username, password) VALUES ('${userName}', '${hashedPassword}');`);
            });
        }
    });
    return { userName }
}

This is the most basic example of dependency inversion, and already you can see how versatile it can make your code. With a simple change, we went from only being able to create one user in our system, to creating an infinite amount of users. What if you could make any part of your code this versatile? In the next section, I’ll show how we can add more versatility to our system, and make the code look nicer.

Refactoring Further

In the above example, the same code that verifies if a user exists also connects with the database. And just like the original version of our code was not very versatile due to the username and password being hard coded, the database connection is also hard coded. Imagine that Bob really likes this web page you made for him and sees an opportunity to allow other ice cream shop owners to use it to track sales in their own shop. Since we don’t want other ice cream shop owners seeing Bob’s sale stats, we’d have to go rewrite the code for each ice cream shop owner so they can have their own database. Or, we could invert dependencies so that the database logic is together and the business logic is together.


function createUser(userName, password, usersStorage) {
    if (usersStorage.usernameExists(userName)) {
        throw 'User already exists';
    } else {
        usersStorage.hashPassword(password, (hashedPassword) => {
            usersStorage.createUser(userName, hashedPassword);
        })
    }
    return { userName }
}

In this refactored code, we can now call the createUser function and inject the usersStorage logic into the function, giving us the ability to run this code against infinite databases (or better yet, any other data storage strategy outside of a mySQL database.)