zazoomauro / node-dependency-injection

The NodeDependencyInjection component allows you to standarize and centralize the way objects are constructed in your application.
https://github.com/zazoomauro/node-dependency-injection/wiki
MIT License
275 stars 34 forks source link

RangeError: Maximum call stack size exceeded (Circular dependency) #167

Closed InfoLorenzo closed 2 years ago

InfoLorenzo commented 2 years ago

Hello, first of all, thanks for the library, the support with YML is awesome.

The Problem

Straight to the point, I get a Max stack size when I inject a service on my class that is already injected.

Being more explicit I have this class, that is my entry point:

User.controllers.RegisterUserController:
    class: ../application/controllers/register.user.controller.ts
    arguments: [ '@Shared.commandBus' ]

Then this one injects the Service ( "Shared.commandBus" ) this one:

  Shared.commandBus:
    class: ../core/infrastructure/cqrs/CommandBus/InMemoryCommandBus.ts
    arguments: [ '@Shared.CommandHandlersInformation' ]

And then "Shared.CommandHandlersInformation" injects an array of services, this way:

  Shared.CommandHandlersInformation:
    class: ../core/infrastructure/cqrs/CommandBus/CommandHandlersInformation.ts
    arguments: [ '!tagged commandHandler' ]

One of the services that this service receives is this one:

  User.commands.CreateUserCommandHandler:
    class: ../application/commands/handlers/create.user.command.handler.ts
    arguments: [ '@User.useCases.CreateUserUseCase' ]
    tags:
      - { name: 'commandHandler' }

And then in this service "@User.useCases.CreateUserUseCase":

  User.useCases.RegisterUserUseCase:
    class: ../application/useCases/register/register.user.usecase.ts
    arguments: [ '@Auth.provider.google.service']

Is where my problem is, this way everything works fine, but when I try to inject "Shared.commandBus" this way:

  User.useCases.RegisterUserUseCase:
    class: ../application/useCases/register/register.user.usecase.ts
    arguments: [ '@Auth.provider.google.service','@Shared.commandBus']

I get the error, here is the stack trace:

uncaughtException RangeError: Maximum call stack size exceeded
    at InstanceManager._findTaggedServices (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:332:20)
    at InstanceManager._resolveServices (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:303:21)
    at C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:262:37
    at Array.forEach (<anonymous>)
    at InstanceManager._resolveArguments (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:261:12)
    at InstanceManager._getNotSyntheticInstanceFromDefinition (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:181:23)
    at InstanceManager.getInstanceFromDefinition (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:169:21)
    at InstanceManager._getExistingInstanceFromId (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:150:27)
    at InstanceManager._getInstanceFromId (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:123:21)
    at InstanceManager.getInstance (C:\Users\loren\WebstormProjects\loren-cqrs-project\node_modules\node-dependency-injection\dist\lib\InstanceManager.js:91:21)

What a tried

Changing version, I saw someone, that had problems with latest version so changed to v2.6.11 and the problem was solved, not my case.

I tried with latest and v2.6.11 and still the same error ( #165 Issue where I took the idea )

I tried to change the service with another, the another one that is just a simple service, that only injects another service on itself, and all works fine.

Tried to clone the service "Shared.commandBus" to something like "Shared.commandBus1" and still the same error.

And tried to inject another service that is "Shared.QuerBus" that does exactly the same, injects on itself another service that loads an array of services....

But rather than "Shared.commandBus" this one is not injected before on the services that injects the service where I get the problem "User.useCases.RegisterUserUseCase"

Versions

Node: 16.13.0 NPM: 8.1.0 Node-dependency-injection: 2.6.11 & 2.7.1

In case that is neccesary I would be glad to open a repo with a minimal reproduction of the problem

zazoomauro commented 2 years ago

@InfoLorenzo looks like you have a circular dependency in your configuration and container cannot be compiled properly.

InfoLorenzo commented 2 years ago

Yes, it was a circular dependency in configuration. I thought that it was going to find the already instanced class of "Shared.commandBus" rathen than creating a new instance again. also I tried to use the factory with a singleton but didn't worked

So, how I solved it? If it's useful to someone:

I just detached the dependency and a I accessed the dependency throught a singleton rather than the node dependency injection container.

My "Shared.CommandHandlersInformation" remains the same, but this time my middle service is the one that imports this one and will act as middleware between "Shared.CommandHandlersInformation" and "Shared.commandBus" that was the ones that had a direct dependency and was generating a circular dependency.

The middle service:

import CommandHandlersInformation from "./CommandHandlersInformation";
import container from "../../../../dependency-injection";

export class CommandHandlersInformationDetacher {

    private static instance: CommandHandlersInformationDetacher;
    public commandHandlerInformation: CommandHandlersInformation;

    private constructor() {
        this.commandHandlerInformation = container.get("Shared.CommandHandlersInformation");
    }

    public static getInstance(): CommandHandlersInformationDetacher {
        if (!CommandHandlersInformationDetacher.instance) {
            CommandHandlersInformationDetacher.instance = new CommandHandlersInformationDetacher();
        }

        return CommandHandlersInformationDetacher.instance;
    }
}

And then on my "Shared.commandBus" instead of add the instance throught the constructor I remain it clean with no dependencies on the configuration:

  Shared.commandBus:
    class: ../core/infrastructure/cqrs/CommandBus/InMemoryCommandBus.ts
    main: InMemoryCommandBus

And then on my "Shared.commandBus" I just access the public property "commandHandlerInformation" from my singleton middle service (CommandHandlersInformationDetacher) and all works fine.

This is the first way that comes to my mind to address this problem, if there is any other way to solve this problem I will be glad to hear and discuss it here.

Also thanks to @zazoomauro for the tip, it was finally a circular dependency in the configuration

zazoomauro commented 2 years ago

@InfoLorenzo no problem!