node.jstypescriptmodel-view-controllercoding-style

Can I create a default object in the constructor of a class in the dependency injection pattern?


I'm starting to develop a rest API in the MVC pattern using NodeJS and Typescript.

In my model layer I created entities, services and repositories. My controller layer is decoupled from the framework and receives a request and response object from an adapter.

In my controller there is a service that must be injected via constructor, however I created a default service in case no service is sent.

import { type HttpResponse } from './Contracts/httpResponse';
import { type HttpRequest } from './Contracts/httpRequest';
import { HealthService } from '../Core/Service/Implementation/health.service';
import type { HealthServiceInterface } from '../Core/Service/Contract/health.service';

export class HealthController {
  private readonly healthService: HealthServiceInterface;

  constructor(healthService?: HealthServiceInterface) {
    this.healthService = healthService ?? new HealthService();
  }

  public async getHealth(request: HttpRequest): Promise<HttpResponse> {
    const service = new HealthService();
    const health = await service.getHealth();

    return {
      statusCode: 200,
      body: health
    }
  }
}

In my service I did the same, I receive a repository as an argument but if the argument is undefined I instantiate a standard repository.

import type { HealthRepositoryInterface } from "../../Repository/Contract/health.repository";
import type { HealthServiceInterface } from "../Contract/health.service";
import type { Health } from "../../Entity/health.entity";
import { HealthRepository } from "../../Repository/Implementation/health.repository";

export class HealthService implements HealthServiceInterface {
  private readonly healthRepository: HealthRepositoryInterface;

  constructor(healthRepository?: HealthRepositoryInterface) {
    this.healthRepository = healthRepository ?? new HealthRepository();
  }

  public async getHealth(): Promise<Health> {
    const health: Health = await this.healthRepository.getHealth();
    return health
  }
}

My question is, is it correct to do it this way? Am I making some architectural error?


Solution

  • This sounds wrong. A dependency injection framework has the purpose to provide the respective dependencies and maintain their life cycle as well. This includes creating instances on demand again with all its own dependencies along the dependency graph and providing these dependencies where needed.

    The respective instances can either be created a new each time (when configured as transient) or if the framework supports other life time scopes they can also be configured as, e.g. singletons, so that you can configure that a certain instance is used everywhere for a specific type.

    But if everything is wired up correctly there shall not be a situation where an object would be assembled with a null/undefined dependency. So the situation your code is handling with an optional repository should never be valid and therefore not needed to be handled.

    Note: some frameworks allow you to configure an instance to always be used for a specific type allowing you to "pre-create" an instance upfront at configuration time.