servicelocatorjs
Version:
Dependency injection approach using Service Locator pattern
507 lines (380 loc) • 12.1 kB
Markdown
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
```javascript
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.
```javascript
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`.
```javascript
ServiceLocator.setMixin({
mixinMethod: function () {}
});
```
##### getMixin (): Object
Return current set mixins.
```javascript
ServiceLocator.getMixin();
```
##### mixin(objectWithMixins?: Object): Object
Set and/or return mixins.
```javascript
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_.
```javascript
ServiceLocator.register('serviceName', function (a, b) {
this.a = a;
this.b = b;
}, true, [1, 2]);
```
```javascript
ServiceLocator.register('serviceName', {
a: 1,
b: 2
});
```
##### registerAll (arrayOfServices: Array): Array<String>
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**.
```javascript
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.
```javascript
ServiceLocator.get('serviceName')
```
##### instantiate (serviceName: String): boolean
Instantiates service by name.
```javascript
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.
```javascript
ServiceLocator.instantiateAll(function (serviceName) {
if (serviceName === 'ServiceName') {
return false;
} else {
return true;
}
})
```
##### getAllInstantiate (): Array<String>
Returns the array of instantiated service objects.
```javascript
ServiceLocator.getAllInstantiate();
```
##### isRegistered (serviceName: String): boolean
Checks whether the service is registered.
```javascript
ServiceLocator.isRegistered('ServiceName');
```
##### isInstantiated (serviceName: String): boolean
Checks whether the service is instantiated.
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
ServiceLocator.unRegisterAll(true);
```
## Example
###Creating a new instance:
####In runtime environment like node.js or io.js:
```javascript
var ServiceLocator = require("servicelocatorjs");
```
####In the browser:
```javascript
var ServiceLocator = window.ServiceLocator;
```
####Print debug information in console:
```javascript
ServiceLocator.printLog(true);
```
####Create mixin for services:
```javascript
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:
```javascript
ServiceLocator.setMixin(mixin);
```
####Create constructors for services:
```javascript
/** @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:
```javascript
ServiceLocator.register('ServiceOne', ServiceOne, true);
```
In `Service Locator` registry its instance looks like this:
```javascript
{
__mixins: ["id", "setState", "getState", "getName"]
getName: function,
getState: function,
id: "ServiceOne",
name: "ServiceOne",
setState: function
}
```
####With lazy instantiation:
```javascript
ServiceLocator.register('ServiceTwo', ServiceTwo, false);
```
No instance, but there is a construction function:
```javascript
{
creator: function ServiceTwo(),
name: "ServiceTwo",
prototype: ServiceTwo
}
```
####Default immediate registration:
```javascript
ServiceLocator.register('ServiceThree', ServiceThree, true, [{mydata: "example information"}]);
```
```javascript
ServiceLocator.get('ServiceThree').data; // {mydata: "example information"}
```
####Create a service object by yourself:
```javascript
var serviceFour = new ServiceFour;
```
####Inject a previously created service object:
```javascript
ServiceLocator.register(serviceFour.name, serviceFour);
// or
ServiceLocator.register('ServiceFour', serviceFour);
```
####Get an instance of service:
```javascript
var ONE = ServiceLocator.get('ServiceOne');
```
####Call a mixin method:
```javascript
ONE.getName(); // "ServiceOne"
```
####Call another mixin method:
```javascript
ONE.setState("launched");
```
####Now call a mixin directly from "ServiceLocator":
```javascript
ServiceLocator.get('ServiceOne').getState(); // "launched"
```
####Service number three have mixin but have no "name" property:
```javascript
ServiceLocator.get('ServiceThree').getName(); // → "Service has no name!"
```
####Get currently instantiated services:
```javascript
ServiceLocator.getAllInstantiate(); // ["ServiceOne", "ServiceThree", "ServiceFour"]
```
#### Instantiate all service objects but "ServiceTwo":
```javascript
ServiceLocator.instantiateAll(function (serviceName) {
if (serviceName === 'ServiceTwo') {
return false;
} else {
return true;
}
});
```
####Now without exceptions:
```javascript
ServiceLocator.instantiateAll(); // → "Instantiate: ServiceTwo"
```
####Get currently instantiated services:
```javascript
ServiceLocator.getAllInstantiate(); // ["ServiceOne", "ServiceTwo", "ServiceThree", "ServiceFour"]
```
###Register multiple service objects
Current state of registry inside `Service Locator`:
```javascript
{
"ServiceFour": ▸Object
"ServiceOne": ▸Object
"ServiceThree": ▸Object
"ServiceTwo": ▸Object
}
```
####Previosly set state:
```javascript
ServiceLocator.get('ServiceOne').getState(); // "launched"
```
####Remove the instance, but keep the service. This removes any non-default set data in the service object:
```javascript
ServiceLocator.removeInstance('ServiceOne');
```
####"ServiceLocator" will instantiate a new instance of the service object:
```javascript
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:
```javascript
var unRegisteredService = ServiceLocator.unRegister('ServiceFive');
```
```javascript
{
__mixins: ["id", "setState", "getState", "getName"],
id: "ServiceFive",
getState: ▸Function,
prop: ▸Array,
setState: ▸Function,
}
```
####Same as above, but without mixins:
```javascript
var unRegisteredServiceWithoutMixins = ServiceLocator.unRegister('ServiceFive', true);
```
Any mentions were removed, so:
```javascript
ServiceLocator.get('ServiceFive'); // null
// → "Service is not registered: ServiceFive"
```
####Delete all registered services from "ServiceLocator" and return an array of their instances:
```javascript
ServiceLocator.unRegisterAll();
```
```javascript
{
"ServiceFive": ▸Object,
"ServiceFour": ▸Object,
"ServiceOne": ▸Object,
"ServiceSix": ▸Object,
"ServiceThree": ▸Object,
"ServiceTwo": ▸Object,
}
```
####Same as above, but returned objects have their mixins removed:
```javascript
ServiceLocator.unRegisterAll(true);
```