protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

Dependency Injection

One pattern that we see again and again in our code is called Dependency injection. Although it sounds and looks a bit daunting at first, it’s actually not that hard to understand and use.

Dependency

I think it’s easiest to start with an example. Here’s a method that peforms a calculation of the saldo of all the accounts of a user

function calculateSaldo(userName, getUser, getAccounts) {
  const user = getUser(userName);
  const accountsForUser = getAccounts(user.id);

  let saldo = accountsForUser.reduce((sum, account) => sum += account.balance, 0);
  // ... and thousands of lines more that makes tricky calculcations
  return saldo
}

In order to calculate this saldo we:

The real business functionality is that last part, summarizing the balance on the accounts. However, in order to do this we depend on some other parts to supply us with the data for the calculation. The two functions are dependencies to us.

Injection

Now, since I wrote the code to get the user and accounts I happen to know that it’s very simple. In fact, we could have written it rigth in the calculateSaldo function if we wanted. Like this:

function calculateSaldo(userName) {
  const user = userDb.find(user => user.name === userName);
  const accountsForUser = accountDb.filter(account => account.userid === user.id);

  let saldo = accountsForUser.reduce((sum, account) => sum += account.balance, 0);
  // ... and thousands of lines more that makes tricky calculcations
  return saldo
}

There are a couple of problems with this code:

For these, and probably more, reasons we can inject the dependencies / things that calculateSaldo needs, in order for calculateSaldo do it’s job.

Hence we can inject two parameters, the two functions that calculateSaldo needs, into the function. Which brings us back to the first example, like this:

function calculateSaldo(userName, getUser, getAccounts) {
  const user = getUser(userName);
  const accountsForUser = getAccounts(user.id);

  let saldo = accountsForUser.reduce((sum, account) => sum += account.balance, 0);
  return saldo
}

Hey hey hey … Jakob had some kind of this and what-have-you

The example above is known as a function injection, where we inject the dependencies into a single function. A special, but common, way of that is what’s known as constructor injection.

Let’s mimic, in part, what we had in the PlayerService by creating a CalculatorService class. It has a constructor like this:

function CalculatorService(userGetter, accountGetter){
  this.getUser = userGetter;
  this.getAccounts = accountGetter;
}

This constructor takes the two dependencies of the class as inparameters - we are constructor injecting the dependencies of CalculatorService.

When we are creating a new class we get an object (instance of the object) back. We are storing the dependencies on the instance by using this, this.getUser = userGetter, for example.

We can then use these dependencies by using this and the function we stored: const accountsForUser = this.getAccounts(user.id); for example.

Last part, in order to create this class we need to do new CalculatorService, like we are doing in the convince-method create that we are exposing outside the CalculatorService

module.exports.create =
  (userGetter, accountGetter) => 
		new CalculatorService(userGetter, accountGetter);

Notice the use of new that is passing the parameters to the create function into the constructor.

Also notice that the CalculatorService knows (and cares) nothing about the dependencies we are passing to the constructor …

Almost. Here is how we are using it:

CalculatorService.prototype.calculateSaldo = function(userName) {
  const user = this.getUser(userName);
  const accountsForUser = this.getAccounts(user.id);

  let saldo = accountsForUser.reduce((sum, account) => sum += account.balance, 0);
  return saldo
}

See how we are using this.getUser to get hold of the method we stored in the instance variable, in the constructor.

So CalculatorService knows (and cares) nothing about the dependencies we are passing to the constructor… except that once we use it we:

How this is accomplished and implemented the CalculatorService couldn’t care less about. Separation of concerns in actions.

Now if we want to use this we can do something like this, stiching all the parts together:

const userGetter = require('./dependencies').getUserFromDb;
const accountGetter = require('./dependencies').getAccountsForUser;

const calc = require('./CalculatorService')
				.create(userGetter, accountGetter);

const marcusSaldo = calc.calculateSaldo('Marcus')
const jakobSaldo = calc.calculateSaldo('Jakob')
console.log(`Marcus has ${marcusSaldo}`);
console.log(`Jakob has ${jakobSaldo}`);

We use the .create-function on the CalculatorService and pass it the getUserFromDb and getAccountsForUser functions as dependencies for the class.

When we call the .calculateSaldo function it will use the dependencies that is injected into the constructor.

Summary

I hope this made dependencies a bit more clear for you. I love to walk you through this if you want to.