Service with dependencies
A service with dependencies has dependencies in the constructor that we need help resolving. Without this resolution process, we can't create the service. Such a service may look like this:
export class Service {
constructor(
Logger logger: Logger,
repository:Repository
) {}
}
In this code, our service has two dependencies. Upon constructing a service, we need one Logger instance and one Repository instance. It would be entirely possible for us to find the Logger instance and Repository instance by typing something like this:
import { Service } from './service'
import logger from './logger';
import { Repository } from './repository';
// create the service
let service = new Service( logger, new Repository() )
This is absolutely possible to do. However, the code is a bit tedious to write every time I want a service instance. When you start to have 100s of classes with deep object dependencies, a DI system quickly pays off.
This is one thing a Dependency Injection library helps you with, even if it is not the main motivator behind its existence. The main motivator for a DI system is to create loose coupling between different parts of the system and rely on contracts rather than concrete implementations. Take our example with the service. There are two things a DI can help us with:
- Switch out one concrete implementation for another
- Easily test our construct
To show what I mean, let's first assume Logger and Repository are interfaces. Interfaces may be implemented differently by different concrete classes, like so:
import { Service } from './service'
import logger from './logger';
import { Repository } from './repository';
class FileLogger implements Logger {
log(message: string) {
// write to a file
}
}
class ConsoleLogger implements Logger {
log(message: string) {
console.log('message', message);
}
}
// create the service
let service = new Service( new FileLogger(), new Repository() )
This code shows how easy it is to switch out the implementation of Logger by just choosing FileLogger over ConsoleLogger or vice versa. The test case is also made a lot easier if you only rely on dependencies coming from the outside, so that everything can therefore be mocked.