@alvitrjs/iocstrapper v0.1.1
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
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