0.1.1 • Published 5 years ago

@alvitrjs/iocstrapper v0.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
5 years ago

IOCStrapper

IOCstrapper is a IoC Container Bootstraper based on adonisjs ioc container to modularize and extend packages or new modules.

Warning

This is not a production ready package, its a working in progress build.

Getting Started

Prerequisites

  • NodeJS : >= 10.16.1
  • NPM : >= 6.9.0

Instalation

You can use NPM or Yarn to install IOCStrapper

npm i --save @alvitrjs/iocstrapper
yarn add @alvitrjs/iocstrapper

Usage

Using Typescript

import Container from '@alvitrjs/iocstrapper/Container';
import IOCStrapper from '@alvitrjs/iocstrapper';

const ioc = new Container();
const iocStrapper = new IOCStrapper(ioc);

Using Javascript

const Container = require('@alvitrjs/iocstrapper/Container');
const IOCStrapper = require('@alvitrjs/iocstrapper');

const ioc = new Container();
const iocStrapper = new IOCStrapper(ioc);

Table of Contents

  1. IOCStrapper
    1. Load Modules
  2. Container
    1. What is IoC
    2. Binding and Singleton
    3. Aliases
    4. Autoload
    5. Use the Container
  3. Service Providers
    1. What is a Service Provider
    2. Creating a Provider
    3. Setting your Providers
  4. In Development
  5. License

IOCStrapper

The IOCStrapper is a class to wrap all the modules in a single instance using Service Providers.

import Container from '@alvitrjs/iocstrapper/Container';
import IOCStrapper from '@alvitrjs/iocstrapper';

const ioc = new Container();

// Class will have the IoC Container
const iocStrapper = new IOCStrapper(ioc);

Load Modules

You can load providers to be used by your application, those modules can be single class module, or an object that has multiple modules to be loaded. To load a module you should pass a path to be loaded, if you pass a single class module, you should pass the path name and file name that has the Service Provider, like so:

loadProviders will throw and Error if the path does not exist

// This will load, based on where iocStrapper is, A FooProvider class from the Providers folder.
iocStrapper.loadProviders(path.join(path.resolve(__dirname), "Providers/FooProvider"));

If you want to load multiple providers, you can use the same method, but instead of passing the path for a class module, you pass a path to an object of multiple modules.

// ./Providers/index.ts
// Your file must export an object named modules, that has a root, and providers, where root is the path where the modules are, and providers are the modules to be loaded.
// Where you can give it a name, and the path that is going to be loaded using the root, so root + provider.
import path from 'path';

export const modules = {
    root: path.resolve(__dirname),
    providers: {
        FooProvider: "FooProvider"
    }
}

// ./index.ts
// This will load, based on where iocStrapper is, A index file from the Providers folder.
iocStrapper.loadProviders(path.join(path.resolve(__dirname), "Providers"));

Container

Before using the Container you should know what it is.

What is IoC

IoC stands for Inversion of Control, meaning that the creation of new classes should be controlled by another source instead of the class that needs, why? because this way you can depend on abstractions making it easier to test your code, making singleton objects and caching files.

Binding and Singleton

You can use both bind and singleton, when using the IoC container. Bind works by creating a new class every time you call the Container for that class, it uses a closure to know what you need, you can use any other class inside the bind or singleton by using the passed ioc to it.

Both bind and singleton will throw an Exception: E_MUST_BE_FUNCTION, if the 2nd parameter is not a function

// This creates a binding for the class foo
ioc.bind('Foo', () => {
    return new Foo();
});

// You can also use the container inside the bind to pass a class to another class if needed
ioc.bind('Foo', (app) => {
    const bar = app.use('Bar');
    return new Foo(bar);
});

The same goes for singleton, but the singleton method creates a class only on the first call, the other calls will be using the cached object.

ioc.singleton('Foo', (app) => {
    return new Foo();
});

You can follow a convention when naming your bindings and singletons, this way it makes easier to remember and use them without the risk of conflicting names. The convetion is using the name of the package for module, then following the folder structure and then the name of the class, by separating using /.

// So if the package name would be Alvitrjs, and the file is inside Foo/Bar and its name is Dah, it would be like so: 
ioc.singleton('Alvitrjs/Foo/Bar/Dah', (app) => {
    return new Dah();
});

Aliases

Aliases are shorthand names for your namespaced bindings.

// So the alias for Alvitrjs/Foo/Bar/Dah would be only Dah
ioc.alias('Alvitrjs/Foo/Bar/Dah', 'Dah');

Autoload

You can autoload paths to be able to use easily without relative or absolute pathing. The files inside the path aren't loaded when called, but when needed to be used. Files that were called already will be cached for subsequent calls.

// This is going to autoload the path app to the App namespace.
// Meaning that instead of using import foo from '../../app/fooclass', you can use like ioc.use('App/FooClass');
// The first one is the namespace and subsequent are folders and files.
ioc.autoload('App', path.join(path.resolve(__dirname), "app"));

// It will return the required FooClass from the app folder.
ioc.use('App/FooClass');

Use the Container

After your Container has the class you need using bind, singleton or autoload, you can use the use method to return that instance.

use will throw an Exception: E_NAMESPACE_NOT_FOUND if the namespace does not exist

// This is going to return a instance of the Dah class
const dah = app.use('Alvitrjs/Foo/Bar/Dah');

Service Providers

What is a Service Provider

A service provider is a way of creating pieces of application in a modular way and injecting into the framework, it works by calling and registering them using the Container.

Creating a Provider

Every provider should extend the ServiceProvider class and obey to an implementation of IServiceProvider, that is a register method and possible a boot method.

If A Provider does not have a register method it will throw an Exception: E_MUST_HAVE_REGISTER_METHOD

import ServiceProvider from '@alvitrjs/iocstrapper/ServiceProvider';

import Foo from 'foopackage/Foo';
import Config from 'foopackage/Foo/Config';

interface IServiceProvider {
    register (): void;
    boot? (): void;
}

export class FooProvider extends ServiceProvider implements IServiceProvider {
    register () {
        // Here you should use the Container to register this service to your application
        this._app.singleton('FooPackage/Foo/Config' () => {
            return new Config();
        });

        this._app.alias('FooPackage/Foo/Config', 'Config');

        this._app.singleton('FooPackage/Foo' () => {
            return new Foo();
        });
    }
    
    // the boot method is meant to set values that your class needs or should change inside the application and modules.
    boot () {
        // Here you can use any service from the application, because boot runs after all providers are registered.
        const config = this._app.use('Config');
        const fooValue = config.get('FOO_VALUE');
        
        const foo = this._app.use('FooPackage/Foo');
        foo.setValue(fooValue);
    }
}

Setting your Providers

After creating your provider you have to set it to the application by exporting a Service Provider class, or an object named modules, that has a root, and providers.

By exporting a single Service Provider class:

import ServiceProvider from '@alvitrjs/iocstrapper/ServiceProvider';

import Foo from 'foopackage/Foo';

interface IServiceProvider {
    register (): void;
    boot? (): void;
}

export class FooProvider extends ServiceProvider implements IServiceProvider {
    register () {
        // Here you should use the Container to register this service to your application
        this._app.singleton('FooPackage/Foo' () => {
            return new Foo();
        });
    }
}

By exporting multiple Service Providers:

import path from 'path';

// Keep in mind that the root will be used to locate your providers files.
export const modules = {
    root: path.resolve(__dirname),
    providers: {
        FooProvider: "FooProvider",
        BarProvider: "BarProvider",
        FooBarProvider: "Foo/BarProvider"
    }
}

Note

This readme is currently being written.

In development

These are what I'm currently working on.

  • Error Handling
  • Testing
  • Call Tracker
  • Performance
  • Security

License

MIT