Search code examples
node.jstypescriptinversifyjs

Splitting inversifyJS config file into multiple files


I recently bumped into a backend project using Typescript and would like to implement IoC principle with the help of Inversify.js. Following the official documentation, I have one huge file named inversify.config.ts containing all my interfaces and classes that implement them:

import "reflect-metadata"
import { Container } from "inversify"
import TYPES from './types';

import { ModuleRepo } from '../repo/interfaces'
import { ModuleARepoImpl } from '../repo/moduleA'
import { ModuleBRepoImpl } from '../repo/moduleB'

import { ModuleService } from '../services/interfaces'
import { ModuleAServiceImpl } from '../services/moduleA'
import { ModuleBServiceImpl } from '../services/moduleB'

const container = Container();

container.bind<ModuleRepo>(TYPES.ModuleARepo).to(ModuleARepoImpl);
container.bind<ModuleRepo>(TYPES.ModuleBRepo).to(ModuleBRepoImpl);

container.bind<ModuleService>(TYPES.ModuleAService).to(ModuleAServiceImpl);
container.bind<ModuleService>(TYPES.ModuleBService).to(ModuleBServiceImpl);

export default container;

One big problem in the above setting is when the project gets complex, more modules are added resulting in a very long config file (imagine you have dozens of modules). My plan is to divide it into smaller config files, with inversify.config.ts remains the main file.

consider the following settings:

./dependencies/interface/index.ts

import { Container } from 'inversify';
export type InversifyContainer = Container

export interface BasicInterface {
        register(container: InversifyContainer): void
        readonly types: Object
    }

./dependencies/moduleA/index.ts

import {InversifyContainer, BasicDependencies} from '../interface';
import { ModuleRepo } from '../../repo/interfaces'
import { ModuleARepoImpl } from '../../repo/moduleA'
import { ModuleService } from '../../services/interfaces'
import { ModuleAServiceImpl } from '../../services/moduleA'

export class ModuleADependencies {
    register(container: InversifyContainer) {
           container.bind<ModuleRepo>(TYPES.ModuleARepo).to(ModuleARepoImpl);
           container.bind<ModuleService>(TYPES.ModuleAService).to(ModuleAServiceImpl);
    }

    readonly types = {
          ModuleARepo: Symbol('ModuleARepo'),
          ModuleAService: Symbol('ModuleAService'),
    }
}

./dependencies/inversify.config.ts

import "reflect-metadata"
import { Container } from "inversify"

import { ModuleADependencies } from './moduleA';
import { ModuleBDependencies } from './moduleB'; // consider moduleB also has the same file

const container = Container();
const registrationList = [ModuleADependencies, ModuleBDependencies];
for (const reg of registrationList) {
    new reg().register(container);
}

export default container;

./dependencies/types.ts

import { ModuleADependencies } from './moduleA';
import { ModuleBDependencies } from './moduleB';

const TYPES = {
    ...(new ModuleADependencies().types),
    ...(new ModuleBDependencies().types),
}

export default TYPES

However, this way I always have an error showing something like Cannot read property of ModuleARepo of undefined from the types. I browsed the internet however nobody seems to care about how lengthy and messy inversify.config.ts would be if it is in a complex project.

Hoping someone can help with this :)


Solution

  • First of all your problem is described in the doc and has a solution.

    Your solution is generally correct but there is a circular dependency

    ./dependencies/types.ts -> ./dependencies/moduleA/index.ts -> ./dependencies/types.ts
    

    In types a new instance of class is created but the module that contain the class definition imports types. You don't list this import but use TYPES.ModuleARepo in bind.

    To avoid it you can make types field static or move it out of the class into a separate exportable object. As a positive side effect of it, there will be no need to instantiate a class in ./dependencies/types.ts.

    Just in case please keep in mind that if you instantiate a class that has a Symbol as a field this symbol is unique for every instance since Symbol('ModuleARepo') !== Symbol('ModuleARepo').

    Playground