I'm new to using InversifyJS and I see a lot of basic examples of a class with a constructor using @inject for dependencies. Like this...
export class Service {
protected depA: DependencyA;
protected depB: DependencyB;
constructor(
@inject(DependencyA) dependencyA: DependencyA,
@inject(DependencyB) dependencyB: DependencyB
) {
this.depA = dependencyA;
this.depB = dependencyB;
}
}
Where those injected dependencies have 0 further dependencies.
However, I have coworkers that don't use that and instead use something like...
private readonly service = container.get<Interface>(TYPES.InterfaceSymbol);
to call any necessary dependency service.
I'd like to better understand when to use one over the other with this kind of sample case. Writing it out as nested lists where the further indented list item is its dependency.
Plus a LoggerService that replaces console.log() and should be injectable into any service.
In this case, when should I use the constructor with @inject params vs the container.get() (or some other means you know that I don't)?
To add to Martin's answer, DI is also about lifetimes. Some classes in a Rest API are naturally request scoped, whereas others, such as middleware classes, are natural singletons.
If a singleton class wants to get an instance for the current HTTP request it is common to use container.get
. Wherever possible though, prefer plain constructor injection.
EXAMPLE API
In case it gives you ideas for your own solution, here are a few code snippets from a Node.js API of mine that is focused on non-functional behaviour:
DI COMPOSITION
When the API starts it registers dependencies and there are a few techniques here. For each type you should think about lifetimes. Generally singletons mean there are more risks that request A could interfere with Request B.
Some objects, such as a ClaimsPrincipal
or LogEntry
are naturally request scoped.
DI RESOLUTION
My API uses Inversify Express Utils, which creates a child container per request, which is also a common pattern, eg .NET uses it.
This class for managing cross cutting concerns uses container.get
, though it is the exception rather than the rule:
ALL OTHER CLASSES
The point of the DI plumbing is to deal with plumbing and then enable simple code. So most classes look like this and are easy to reason about, and also nicely testable: