servicelocatorjs

Dependency injection approach using Service Locator pattern

Usage no npm install needed!

<script type="module">
  import servicelocatorjs from 'https://cdn.skypack.dev/servicelocatorjs';
</script>

README

Service Locator

Implementation of the dependency injection approach using the Service Locator pattern.

Service Locator is a dependency injection pattern. The Service Locator pattern does not describe how to instantiate the services. It describes a way to register services and locate them in a global point of access. A Service Locator should be able to locate a service using a central registry without knowing its concrete type. For example, it might use a string key which on request returns the object depending on code initialization. This allows you to replace the concrete implementation of the dependency without modifying the objects.

Advantages:

  • option of adding extra attributes and methods using mixin pattern to all objects registered in Service Locator;
  • option of lazy instantiation;
  • un-registering instances;
  • un-registering objects;
  • applications can optimize themselves at run-time by selectively adding and removing items from the Service Locator;
  • large sections of a library or an application can be completely separated; the only link between them becomes the registry;
  • it solves the drawback of factories, allowing to manage the creation of objects automatically and centrally.

Drawbacks:

  • your classes have an extra dependency on Service Locator;
  • you have to write additional code to add service references to the locator before your service objects use it;
  • objects placed in the registry are black boxed, this makes it harder to detect and recover from their errors, and may make the system as a whole less reliable;
  • sometimes registry can be a security vulnerability, because it allows outsiders to inject code into the application;
  • the source code has added complexity, this makes the source code more difficult to comprehend;
  • registry hides the object dependencies, causing errors when dependencies are missing;
  • registry makes the code harder to test, since all tests need to interact with the same global Service Locator; to set the fake dependencies of a service object under test.

API

Require in Node

var ServiceLocator = require("servicelocatorjs");
printLog (flag?: boolean)

Takes true/false values as a parameter. When true, writes information about events and channels into the console.

ServiceLocator.printLog(true);
setMixin (objectWithMixins: Object)

Takes an object as a parameter. The object contains a set of additional properties and/or methods, which have to contain all objects registered in Service Locator.

ServiceLocator.setMixin({
    mixinMethod: function () {}
});
getMixin (): Object

Return current set mixins.

ServiceLocator.getMixin();
mixin(objectWithMixins?: Object): Object

Set and/or return mixins.

ServiceLocator.mixin({
    mixinMethod: function () {}
});
register (serviceName: String, serviceObject: Function|Object, instantiate?: boolean, constructorArguments?: Array): boolean

Registers an object serviceObject under the name serviceName. The flag instantiate shows whether lazy instantiation is required to request the object from Service Locator. By default instantiate is true.

ServiceLocator.register('serviceName', function (a, b) {
    this.a = a;
    this.b = b;
}, true, [1, 2]);
ServiceLocator.register('serviceName', {
    a: 1,
    b: 2
});
registerAll (arrayOfServices: Array): Array

Calls the register function for each element of arrayOfServices. Each element of the array must contain one of the ID or id properties for defining the object name, and service/object/creator for defining the object under registration. There is optional instantiate.

ServiceLocator.registerAll([
    {
        /**
         * @constructor
         * @param {*} value
         */
        creator: function (value) {
            this.prop = value;
        },
        id: 'ServiceFive',
        instantiate: false
    },
    {
        service: {
            prop: 'Some property'
        },
        id: 'ServiceSix'
    }
]);
get (serviceName: String): null|Object

Returns the instance of a registered object with an indicated serviceName or creates a new one in case of lazy instantiation.

ServiceLocator.get('serviceName')
instantiate (serviceName: String): boolean

Instantiates service by name.

ServiceLocator.instantiate('serviceName')
instantiateAll (filter?: Function)

Instantiates and returns all registered objects. Can take the filter function as an argument. The filter function must return the logical value. In case filter is predefined, only the services that underwent the check will be instantiated.

ServiceLocator.instantiateAll(function (serviceName) {
    if (serviceName === 'ServiceName') {
        return false;
    } else {
        return true;
    }
})
getAllInstantiate (): Array

Returns the array of instantiated service objects.

ServiceLocator.getAllInstantiate();
isRegistered (serviceName: String): boolean

Checks whether the service is registered.

ServiceLocator.isRegistered('ServiceName');
isInstantiated (serviceName: String): boolean

Checks whether the service is instantiated.

ServiceLocator.isInstantiated('ServiceName');
removeInstance (serviceName: String): boolean

Deletes a service instance with an indicated serviceName. Returns false in case the service with the indicated serviceName is not found or has no instance. This does not remove the service itself, but only its instances.

ServiceLocator.removeInstance('ServiceName');
unRegister (serviceName: Array|String, removeMixins?: boolean): null|Object

Deletes a service named serviceName from Service Locator and returns its instance. The flag removeMixins points at the necessity to delete the added mixin properties.

ServiceLocator.unRegister('ServiceName', true);
unRegisterAll(removeMixins?: boolean): Object

Deletes all registered services from Service Locator and returns the array of their instances. The flag removeMixin points at the necessity to delete the added properties in the services that will be deleted.

ServiceLocator.unRegisterAll(true);

Example

Creating a new instance:

In runtime environment like node.js or io.js:

var ServiceLocator = require("servicelocatorjs");

In the browser:

var ServiceLocator = window.ServiceLocator;

Print debug information in console:

ServiceLocator.printLog(true);

Create mixin for services:

var mixin = {
    /**
     * Set in service object new property <_state> for further use
     * @param {*} value
     */
    setState: function (value) {
        this._state = value;
    },
    /**
     * Get <_state> property from service object
     * @return {*}
     */
    getState: function () {
        return '_state' in this ? this._state : undefined;
    },
    /**
     * Get service object name
     * @return {String}
     */
    getName: function () {
        return 'name' in this ? this.name : 'Service has no name!';
    }
};

Set it for ServiceLocator:

ServiceLocator.setMixin(mixin);

Create constructors for services:

/** @constructor */
function ServiceOne() {
    this.name = 'ServiceOne'; // This property is not required. Made for example
}

/** @constructor */
function ServiceTwo() {
    this.name = 'ServiceTwo';
    this.serviceFunction = function () {
        return 'Service number two function';
    };
}
/**
 * @param {*=} data
 * @constructor
 */
function ServiceThree(data) {
    // Service without <name> property
    this.data = data;
}
/** @constructor */
function ServiceFour() {
    this.name = 'ServiceFour';
}

Registering service objects in "ServiceLocator"

With instantiation immediately after registration:

ServiceLocator.register('ServiceOne', ServiceOne, true);

In Service Locator registry its instance looks like this:

{
    __mixins: ["id", "setState", "getState", "getName"]
    getName: function,
    getState: function,
    id: "ServiceOne",
    name: "ServiceOne",
    setState: function
}

With lazy instantiation:

ServiceLocator.register('ServiceTwo', ServiceTwo, false);

No instance, but there is a construction function:

{
    creator: function ServiceTwo(),
    name: "ServiceTwo",
    prototype: ServiceTwo
}

Default immediate registration:

ServiceLocator.register('ServiceThree', ServiceThree, true, [{mydata: "example information"}]);
ServiceLocator.get('ServiceThree').data; // {mydata: "example information"}

Create a service object by yourself:

var serviceFour = new ServiceFour;

Inject a previously created service object:

ServiceLocator.register(serviceFour.name, serviceFour);
// or
ServiceLocator.register('ServiceFour', serviceFour);

Get an instance of service:

var ONE = ServiceLocator.get('ServiceOne');

Call a mixin method:

ONE.getName(); // "ServiceOne"

Call another mixin method:

ONE.setState("launched");

Now call a mixin directly from "ServiceLocator":

ServiceLocator.get('ServiceOne').getState(); // "launched"

Service number three have mixin but have no "name" property:

ServiceLocator.get('ServiceThree').getName(); // → "Service has no name!"

Get currently instantiated services:

ServiceLocator.getAllInstantiate(); // ["ServiceOne", "ServiceThree", "ServiceFour"]

Instantiate all service objects but "ServiceTwo":

ServiceLocator.instantiateAll(function (serviceName) {
    if (serviceName === 'ServiceTwo') {
        return false;
    } else {
        return true;
    }
});

Now without exceptions:

ServiceLocator.instantiateAll(); // → "Instantiate: ServiceTwo"

Get currently instantiated services:

ServiceLocator.getAllInstantiate(); // ["ServiceOne", "ServiceTwo", "ServiceThree", "ServiceFour"]

Register multiple service objects

Current state of registry inside Service Locator:

{
    "ServiceFour":  ▸Object
    "ServiceOne":   ▸Object
    "ServiceThree": ▸Object
    "ServiceTwo":   ▸Object
}

Previosly set state:

ServiceLocator.get('ServiceOne').getState(); // "launched"

Remove the instance, but keep the service. This removes any non-default set data in the service object:

ServiceLocator.removeInstance('ServiceOne');

"ServiceLocator" will instantiate a new instance of the service object:

ServiceLocator.get('ServiceOne').getState(); // undefined
// → "Instantiate: ServiceOne"

As you can see, previously saved data won't be brought back.

Deletes a service from "ServiceLocator" and returns its instance:

var unRegisteredService = ServiceLocator.unRegister('ServiceFive');
{
    __mixins: ["id", "setState", "getState", "getName"],
    id:       "ServiceFive",
    getState: ▸Function,
    prop:     ▸Array,
    setState: ▸Function,
}

Same as above, but without mixins:

var unRegisteredServiceWithoutMixins = ServiceLocator.unRegister('ServiceFive', true);

Any mentions were removed, so:

ServiceLocator.get('ServiceFive'); // null
// → "Service is not registered: ServiceFive"

Delete all registered services from "ServiceLocator" and return an array of their instances:

ServiceLocator.unRegisterAll();
{
    "ServiceFive":  ▸Object,
    "ServiceFour":  ▸Object,
    "ServiceOne":   ▸Object,
    "ServiceSix":   ▸Object,
    "ServiceThree": ▸Object,
    "ServiceTwo":   ▸Object,
}

Same as above, but returned objects have their mixins removed:

ServiceLocator.unRegisterAll(true);