Hunter Henrichsen

Hunter Henrichsen

Search Circle
< lecture

Lecture 23 - Dependency Injection

posted about 1 year ago 6 min read

Lecture 23 - Dependency Injection#

Announcements#

News#

Code Organization#

You’re the only gatekeeper between your entire codebase living in one file, and being impossible to understand (or merge with other branches) without understanding everything. I’ve seen a few codebases like that in my time; I’ve written a few codebases like that, even.

Why should I organize my code?#

A better way of asking this question is “why should I have standards about where my code should live”? In my opinion, there are a variety of reasons, including:

How should I organize my code?#

I propose a fairly straightforward model, pun intended:

As a codebase grows, I find that it makes sense to make things more granular, but at that point hopefully you have enough data that it’s clear where the next ideal separate layers or sublayers are.

For example, lets say I’m building a blog site for myself.

Now for a couple trickier examples; where should these go?

Depending on the technologies you’re using, the model and data layers may be blurry. In Django, for example, the model classes that represent your data are the same ones that set up the table and query structure in your database.

The important part is that there’s separation here between logical units; I don’t want all of my database queries in one file, or many duplicated queries scattered through tens or hundreds of endpoints. But now that everything’s all split up, how do I actually use these small pieces? That’s where Dependency Injection is useful.

Dependency Injection#

I first heard about DI 8 years ago while I was trying (and failing, spectacularly) to write a Minecraft plugin. Someone introduced me to the term, and I was able to fix my issue, but I didn’t really understand the principle until much later in my software engineering career.

Dependency Injection is the idea that instead going out and getting what you need to do whatever is needed, instead you ask nicely for it. Here’s a relatively simple example:

public class Authenticator {
protected UserRepository userRepo;
public Authenticator() {
this.userRepo = new UserRepository(new DatabaseConnection());
}
public boolean isAuthenticated(String cookie) {
try {
var user = this.userRepo.getUserByCookie(cookie);
if (user == null) {
return false
}
return user.cookieExpiration > System.currentTimeMillis();
}
catch (ConnectionError error) {
return false;
}
}
}

In this class, I’m constructing my own instance of a UserRepository and a DatabaseConnection that I use. If I need to use some other kind of UserRepository (perhaps a mock version that I can use for tests, rather than connecting to the database), I need to extend this class and use that instead. While this works, there are problems here:

  1. In order to change the functionality, I need to extend the class.
  2. If the same Cryptography is used elsewhere, I’m wasting memory by keeping multiple instances of it around.

Why not a Singleton?#

One solution might be to make my UserRepository a singleton, and just use that instance instead, right?

public class Authenticator {
public boolean isAuthenticated(String cookie) {
try {
var user = UserRepository.INSTANCE.getUserByCookie(cookie);
if (user == null) {
return false
}
return user.cookieExpiration > System.currentTimeMillis();
}
catch (ConnectionError error) {
return false;
}
}
}

It certainly simplifies the code, but the problem is that now we have what amounts to a global variable, and we still haven’t solved the problem of changes to functionality very well. Java does okay at preventing global variables, but other languages don’t do as well and can make things even worse than this.

For example, In JavaScript I’ve written this at the bottom of a file:

let instance: Harbinger;
export function getInstance() {
return instance;
}

Which is just a global variable with extra steps, because I can only change its value from within this file.

Inject Your Dependencies#

I feel like I’ve beaten around the bush enough; what if we did this instead?

public class Authenticator {
protected UserRepository userRepo;
public Authenticator(UserRepository userRepo) {
this.userRepo = userRepo
}
public boolean isAuthenticated(String cookie) {
try {
var user = this.userRepo.getUserByCookie(cookie);
if (user == null) {
return false
}
return user.cookieExpiration > System.currentTimeMillis();
}
catch (ConnectionError error) {
return false;
}
}
}

Another name for this is Inversion of Control, which I feel is accurate. My class is no longer in control of what it needs, which makes it far easier to write more flexible code.

We’ll go through this example in class.

Reading#