Package Information: kangojs
Describe your feature
Implement a dependency injection (DI) / Inversion of Control (IoC) system into KangoJS.
This would mean you could mark dependencies to inject into controlllers, services etc and KangoJS would handle the depedency creation and injection.
This DI must be recursive, so the application must work down the dependency tree (controller -> service -> other services/helpers etc) and handle injecting everything.
Is your suggestion related to any problems?
For testing (unit tests and e2e) you need to be able to access the dependency instances that are part of the application and also potentially overwrite / mock dependencies.
This will be useful for a number of things during testing:
- For database setup/teardown during e2e testing you could re-use the same database service that the application uses to create content rather than having to write custom things for testing or creating duplicate instances.
- Testing certain states may rely on knowing/editing internal application state so having access to the same dependency instance that is being used by the services/controllers could let you view/edit application state for tests.
Additional context
I think this would be done the same / a similar way to how it's done in NesJS or InversifyJS:
import { Inject } from "@kangojs/core";
import { UserService } from "./users.service";
export class UserController {
constructor(
@Inject(UserService) userService: UserService
)
}
import { Inject } from "@kangojs/core";
import { UserDatabase } from "./users.database";
export class UserService {
constructor(
@Inject(UserDatabase) userDatabase: UserDatabase,
@Inject(RandomService) randomService: RandomService
)
}
import { Inject } from "@kangojs/core";
export class UserDatabase {
constructor(
@Inject(Config) config: Config
)
}
Some notes:
@Inject
decorator can be used to mark a dependency as injectable and tells KangoJS what class should be instantiated, passed to the class and stored in a centeral IoC container for later use.
- I will need to consider if/how to implement singleton injection vs multiple instance injection / factories etc.
- DI will work using an IoC container, meaning dependencies will be stored/managed in one centeral container and injected from this container. This means that there could be an interface to let you fetch dependencies from the KangoJS class. For example:
const kangoJS = new KangoJS();
await kangoJS.bootstrap();
const userDatabase = await kangoJS.getDependency<UserDatabase>(UserDatabase);
Maybe there could also then be a way to manually add/overwrite dependencies too? I don't think this would be a paticualr focus for an MVP of this feature though:
const kangoJS = new KangoJS();
await kangoJS.bootstrap();
class MockUserDatabase implements UserDatabaseInterface {
isUserValid(user: User) {
if (user.id === "test-user-id") {
return true;
}
return this.super.isUserValid(user);
}
}
// Set dependency to use, but still let KangoJS handle instantiation via the DI system
// as maybe MockUserDatabase still relies on other injected services
await kangoJS.setDependency(UserDatabase, MockUserDatabase);
// Maybe you'd also need a way to full overwrite/bypass KangoJS's DI system
await kangoJS.setDependencyInstance(UserDatabase, new MockUserDatabase(new TestConfig()));