When to use container.get vs constructor @inject?

1.3k Views Asked by At

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.

  • RootService
    • BusinessLogicAService
      • RepoAService
        • GeneralDbConnectionService
    • BusinessLogicBService
      • RepoBService
        • GeneralDbConnectionService

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)?

2

There are 2 best solutions below

4
On BEST ANSWER

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:

0
On

For most use cases you should rely on automated dependency injection using @inject over manually querying the container using container.get. The point of any dependency injection framework is that you can just write your classes with their dependencies and let the framework handle all the wiring for you, without having to write boiler plate code. You shouldn't need to query the container manually unless you're in an exotic situation where the framework can't figure out the dependency graph.