Thursday, 11 May 2017

Best way to inject a function using InversifyJS

There's an official recipe to inject a function using InversifyJS. Basically, we define a helper function that will return a curried version of a given function func with all its dependencies resolved using container.get(...):

import { container } from "./inversify.config"

function bindDependencies(func, dependencies) {
    let injections = dependencies.map((dependency) => {
        return container.get(dependency);
    });

    return func.bind(func, ...injections);
}

export { bindDependencies };

And we use it like this:

import { bindDependencies } from "./utils/bindDependencies";
import { TYPES } from "./constants/types";

function testFunc(something, somethingElse) {
    console.log(`Injected! ${something}`);
    console.log(`Injected! ${somethingElse}`);
}

testFunc = bindDependencies(testFunc, [TYPES.something, TYPES.somethingElse]);

export { testFunc };

I'd like to inject the function automatically, without explicitly provide it's dependencies to bindDependencies, maybe based on the function's parameters names. Something like this:

import { default as express, Router } from 'express';

import { bindDependencies } from '../injector/injector.utils';

import { AuthenticationMiddleware } from './authentication/authentication.middleware';
import { UsersMiddleware } from './users/users.middleware';

import { ENDPOINTS } from '../../../../common/endpoints/endpoints.constants';


function getRouter(
    authenticationMiddleware: AuthenticationMiddleware,
    usersMiddleware: UsersMiddleware,
): express.Router {
    const router: express.Router = Router();

    const requireAnonymity: express.Handler = authenticationMiddleware.requireAnonymity.bind(authenticationMiddleware);
    const requireAuthentication: express.Handler = authenticationMiddleware.requireAuthentication.bind(authenticationMiddleware);

    router.route(ENDPOINTS.AUTHENTICATION)
        .put(requireAnonymity, authenticationMiddleware.login.bind(authenticationMiddleware))
        .delete(requireAuthentication, authenticationMiddleware.logout.bind(authenticationMiddleware));

    router.route(ENDPOINTS.USER)
        .put(requireAnonymity, usersMiddleware.register.bind(usersMiddleware))
        .post(requireAuthentication, usersMiddleware.update.bind(usersMiddleware))
        .delete(requireAuthentication, usersMiddleware.remove.bind(usersMiddleware));

    return router;
}

const router: express.Router = invoke(getRouter);

export { router as Router };

Note in this case I just want to call the injected function once and get its return value, which is what I'm exporting, so maybe there's a better way to do this without wrapping the code in a function, but I though using container.get(...) directly outside of my composition root was not a good idea, as the dependencies for this module would not be clear and may be spread across all its lines. Also, exporting that function would simplify the tests.

Going back to my issue, my invoke function looks like this:

function invoke<T>(fn: Function): T {
    const paramNames: string[] = getParamNames(fn);

    return fn.apply(null, paramNames.map((paramName: string)
        => container.get( (<any>container).map[paramName.toUpperCase()] ))) as T;
}

For getParamNames I use one of the solutions proposed here: How to get function parameter names/values dynamically from javascript

(<any>container).map is an object I create in my inversify.config.ts after creating my container, which links a string representation of all my dependencies' keys and the real key, regardless of its type (in this case, just symbol or Function):

const container: Container = new Container();

container.bind<FooClass>(FooClass).toSelf();

...

const map: ObjectOf<any> = {};

(<any>container)._bindingDictionary._map
    .forEach((value: any, key: Function | symbol) => {
        map[(typeof key === 'symbol'
            ? Symbol.keyFor(key) : key.name).toUpperCase()] = key;
    });

(<any>container).map = map;

Anyone knows if there's a better way to do this or if there's any important reason not to do it?



via Danziger

No comments:

Post a Comment