An implementation of Dependency Injection pattern in a way, which enables its usages in a simplest way possible.
[!NOTE] DI decreases interdependence between modules, what leads to simplifying of tests writing and application logic.
Looks as follows:
// Module's dependencies listed as function parameters.
// user_controller.js - a SYNCHRONOUS module:
exports.UserController = ($conf, DataProvider) => { // <--- dependencies are in braces
// ...
router.get("/get-some-data", async (req, res, next) => {
// ...
const result = await DataProvider.getSomeData();
// ...
});
return router;
}
// data_provider.js - an ASYNCHRONOUS module:
exports.DataProvider = async ($conf) => { // <--- dependencies are in braces
// YOUR PREFERRED DB CONNECTOR
return connector;
}
// server.js
const express = require("express");
const di = require("@teamteanpm2024/distinctio-voluptate-veritatis");
di.init((ctx) => {
ctx.registerOne(require(process.env.APP_CONFIG_PATH), "$conf");
ctx.registerAll(require("./data_provider"));
ctx.registerAll(require("./user_controller"));
return ctx.invoke();
// use an object destruction to get a list of specific modules here, when needed
}).then(({ $conf, UserController }) => { // <----
const server = express();
// ...
server.use("/user", UserController);
// ...
server.listen($conf.server.port, () => {
console.log(`Server is listening at ${$conf.server.port} port.`);
});
});
A runnable working example could be found here.
@teamteanpm2024/distinctio-voluptate-veritatis exposes next 4 functions to the end user:
To define a dependency, create a function and export it under any name. Names MUST be unique among each other.
exports.SomeModule = () => { /**/ }
exports.SomeModule = async () => { /**/ }
// or
exports.SomeModule = () => {
// ...
return new Promise((resolve, reject) => { /**/ });
}
[!NOTE] If a function returns a Promise, it will be automatically converted into async dependency.
To define module's dependencies, list them as typical function parameters, assuming they are defined in the same way, described already above. They can have a list of it's own dependencies too:
exports.Module_A = (Module_B, Module_C, ... Module_N) => { /**/ }
There is nothing special about this, since it is a plain Node.js export:
exports.Module_A = (Module_C, Module_B) => { /**/ }
exports.Module_B = (Module_C) => { /**/ }
exports.Module_C = () => { /**/ }
const di = require("@teamteanpm2024/distinctio-voluptate-veritatis");
di.init((ctx) => {
ctx.registerAll(require("./some_module_1"));
ctx.registerAll(require("./some_module_2"));
return ctx.invoke();
}).then((ctx) => {
// ...
});
di.init resolves with an object which provides an access to all registered modules. An object destruction could be applied for receiving a list of specific modules:
di.init((ctx) => {
// ...
return ctx.invoke();
}).then(({ module_1, modul_2, module_n }) => {});
[!NOTE] Before returning a result, ctx.invoke() will wait for all asynchronous dependencies to resolve.
It is also possible to register an anonymous function or an object, but a name of a module MUST be set additionally. For this purpose serves registerOne:
// module_a.js
module.exports = () => { /**/ }
// index.js
di.init((ctx) => {
ctx.registerOne(require("./module_a"), "MyModule");
return ctx.invoke();
}).then((ctx) => {
// ...
});
/**
* @param { Function } callback - A function to provide a context with.
*
* @returns Must return a result(Promise) of *** invoke *** call.
*/
/**
* @param { Object } dependencies - An map of dependencies.
*
* @returns undefined.
*/
/**
* @param { Object } dependency - A single dependency map.
* @param { String } name - A module name to set.
*
* @returns undefined.
*/
/**
* @returns A Promise, which resolves with DI context object.
*/