DEV Community

Tu Bui
Tu Bui

Posted on • Edited on

Safely restructure your codebase with Dependency Graphs

Using "inversify" is a key to create Deoencency-Graph-Binding

import { Container } from "inversify";

var container = new Container();
export { container };

Enter fullscreen mode Exit fullscreen mode
import { interfaces, namedConstraint, taggedConstraint, traverseAncerstors, typeConstraint } from "inversify";

class BindingHelper
{
    targetNamed(request: interfaces.Request, name: string | number | symbol): boolean
    {
        return request != null && request.target.matchesNamedTag(name.toString());
    }

    targetTagged(request: interfaces.Request, key: string | number | symbol, value: unknown): boolean
    {
        return request != null && request.target.matchesTag(key)(value);
    }

    targetIsDefault(request: interfaces.Request): boolean
    {
        return request != null && request.target != null && !request.target.isNamed() && !request.target.isTagged();
    }

    injectInto(request: interfaces.Request, parent: (NewableFunction | string)): boolean
    {
        return request != null && typeConstraint(parent)(request.parentRequest);
    }

    parentNamed(request: interfaces.Request, name: string | number | symbol): boolean
    {
        return request != null && namedConstraint(parent)(request.parentRequest);
    }

    parentTagged(request: interfaces.Request, tag: string | number | symbol, value: unknown): boolean
    {
        return request != null && taggedConstraint(tag)(value)(request.parentRequest);
    }

    anyAncestorIs(request: interfaces.Request, ancestor: (NewableFunction | string)): boolean
    {
        return traverseAncerstors(request, typeConstraint(ancestor));
    }

    noAncestorIs(request: interfaces.Request, ancestor: (NewableFunction | string)): boolean
    {
        return !traverseAncerstors(request, typeConstraint(ancestor));
    }

    anyAncestorTagged(request: interfaces.Request, tag: string | number | symbol, value: unknown): boolean
    {
        return traverseAncerstors(request, taggedConstraint(tag)(value));
    }

    noAncestorTagged(request: interfaces.Request, tag: string | number | symbol, value: unknown): boolean
    {
        return !traverseAncerstors(request, taggedConstraint(tag)(value));
    }

    anyAncestorNamed(request: interfaces.Request, name: string | number | symbol): boolean
    {
        return traverseAncerstors(request, namedConstraint(name));
    }

    noAncestorNamed(request: interfaces.Request, name: string | number | symbol): boolean
    {
        return !traverseAncerstors(request, namedConstraint(name));
    }
}

var when = new BindingHelper();
export { when };

Enter fullscreen mode Exit fullscreen mode
import { Container, interfaces } from "inversify";
import { when } from "./context-binding-helper";


interface DependencyBranch
{
    from(type: any): DependencyGraphHelper;
}

class DependencyGraphHelper implements DependencyBranch
{
    private container: Container;
    private graphs: any[] = [];
    private isBranching: boolean;
    private branchNode: any;

    bind(identifier: any, type: any, isConstant: boolean = false): DependencyGraphHelper
    {
        if (this.isBranching)
        {
            console.error('Please specify which class to be branched from. Parameters: ', identifier, ' ', type);
        }
        var parent = this.graphs[this.graphs.length - 1];
        var node = { identifier: identifier, type: type, parent: parent, isConstant: isConstant };
        this.graphs.push(node);
        return this;
    }

    branch(identifier: any, type: any, isConstant: boolean = false): DependencyBranch
    {
        if (this.graphs.length == 0)
        {
            console.error('Dependency graph cannot start with a "branch". It must start with a "bind" function.Parameters: ', identifier, ' ', type);
        }
        if (this.isBranching)
        {
            console.error('Please specify which class to be branched from. Parameters: ', identifier, ' ', type);
        }
        this.isBranching = true;
        this.branchNode = { identifier: identifier, type: type, isConstant: isConstant };
        return this;
    }

    from(type: any): DependencyGraphHelper
    {
        if (this.isBranching == false)
        {
            console.error('A "from" function must be called after a "branch" function. Parameters: ', type);
        }
        this.isBranching = false;
        var parent = this.graphs.filter(i => i.type == type).pop();
        this.branchNode.parent = parent;
        this.graphs.push(this.branchNode);
        return this;
    }

    registerTo(container: Container): void
    {
        if (this.isBranching)
        {
            console.error('Please specify which class to be branched from before call "register" function.');
        }
        this.container = container;
        for (let index = 0; index < this.graphs.length; index++)
        {
            const graph = this.graphs[index];
            this.registerGraph(graph);
        }
        this.graphs = [];
        this.container = null;
    }

    private registerGraph(graph: any): void
    {
        if (graph.isConstant)
        {
            this.bindToConstant(graph);
        }
        else
        {
            this.bindTo(graph);
        }
    }

    private bindTo(graph: any): void
    {
        this.container.bind(graph.identifier).to(graph.type).when(request =>
        {
            return this.recursiveCheckBindingCondition(request, graph);
        });
    }

    private bindToConstant(graph: any): void
    {
        this.container.bind(graph.identifier).toConstantValue(graph.type).when(request =>
        {
            return this.recursiveCheckBindingCondition(request, graph);
        });
    }

    private recursiveCheckBindingCondition(request: interfaces.Request, graph: any): boolean
    {
        if (graph.parent == null)
        {
            return true;
        }
        var injected = when.injectInto(request, graph.parent.type) && this.recursiveCheckBindingCondition(request.parentRequest, graph.parent);
        return injected;
    }
}

var dependencyGraph = new DependencyGraphHelper();
export { dependencyGraph };


Enter fullscreen mode Exit fullscreen mode
import { dependencyGraph } from "../../common/ioc-helper/dependency-graph-helper";
import { container } from "../../ioc/ioc-container";
import { MonkeyClimb } from "../monkey/monkey-climb";
import { MonkeyControl } from "../monkey/monkey-control";
import { MonkeyEat } from "../monkey/monkey-eat";
import { MonkeyFindFood } from "../monkey/monkey-find-food";
import { MonkeySwingThroughTheTree } from "../monkey/monkey-swing-through-the-tree";
import { MONKEY } from "./animal-ioc-config";

export default class DependenceGraphBinding
{
    public register(): void
    {
        this.registerMonkey();
    }

    private registerMonkey(): void
    {
        dependencyGraph
            .bind(MONKEY.CONTROL,MonkeyControl)
                .branch(MONKEY.EAT,MonkeyEat).from(MonkeyControl)
                .branch(MONKEY.FIND_FOOD,MonkeyFindFood).from(MonkeyControl)
                    .branch(MONKEY.FIND_FOOD,MonkeyClimb).from(MonkeyFindFood)
                    .branch(MONKEY.FIND_FOOD,MonkeySwingThroughTheTree).from(MonkeyFindFood)
            .registerTo(container);
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)