2

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

WhatAName
  • 23
  • 2

2 Answers2

1

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:

Gary Archer
  • 22,534
  • 2
  • 12
  • 24
  • I'll have to go through this further when I have more time to make sure I understand, which I think I do. However, how does one unit test a class with jest when its constructor has @inject a service that has it's own constructor of injected dependencies? I remember attempting to do this and had to mock nested dependency after nest dependency. – WhatAName Nov 02 '21 at 17:33
  • You typically replace infrastructure dependencies of the class under test with mocks. Eg if testing class A which depends on B which depends on C, you would only mock the B dependency and nake the mock B return hard coded results. – Gary Archer Nov 02 '21 at 19:02
  • Would you be able to provide an example of this? While I understand what you're saying (and why), I'm not certain on the how. – WhatAName Nov 04 '21 at 17:26
  • There is a good online example [here](https://dev.to/codedivoire/how-to-mock-an-imported-typescript-class-with-jest-2g7j), where a class called `SoundPlayer` is mocked - even though it could have a tree of dependencies, the mock SoundPlayer is a no-op and could return a hard coded result for testing. I have never used jest but I've used DI in APIs a lot - it is a pattern that provides very good test options – Gary Archer Nov 04 '21 at 19:17
0

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.

Martin Devillers
  • 17,293
  • 5
  • 46
  • 88