Card 01/ 05

ConceptDifficulty: Beginner1 min

Hard-Coded Dependencies and Inversion of Control

One new keyword inside a class ties it to a database, in production and in every test.

A dependency is another object a class needs to do its work, such as the repository a service reads rides from. When the class creates that object itself with new, it is tied to that one implementation. Inversion of Control is the principle that fixes this: something other than the class decides which object it works with.

RideService needs a place to look rides up. Somewhere inside it, a line reads new PostgresRideRepository() (a repository that talks to a Postgres database), and from that moment RideService and Postgres are one thing: you cannot have one without the other, in production or in a test.

java
class RideService {
    private final PostgresRideRepository repository = new PostgresRideRepository();

    Ride getRide(String id) {
        return repository.findById(id);
    }
}

A test for getRide now needs a real, reachable Postgres database, because that one line leaves no other way in. Swap the database for a different provider later, and every class written like this has to be edited, not only the one that talks to the database directly.

Dependency Injection is the technique that carries the principle out: the class receives its dependency, usually as a constructor parameter, instead of constructing it. In a Spring application, the part that creates objects and hands them in is called the container, and the objects it manages are called beans.

Not every new is a problem. Creating a value, such as new Ride(id), is fine: a ride is data the class works with, not a service it depends on to do its job.

So a class should declare what it needs rather than choose it. That one change is what lets a test run the class with nothing real behind it.