Created
September 1, 2014 01:11
-
-
Save 6174/303dde3221af4560ab4b to your computer and use it in GitHub Desktop.
ServiceRegistry pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/** | |
* The <code>AliasRegistry</code> is provides access to the aliases used within | |
* the application. | |
* | |
* <p>An alias is simply an alternate logical name for a class, such that specifying | |
* this logical name in your source code, whether it be Javascript, HTML or CSS, will | |
* cause the class to be bundled and sent to the browser. It is therefore, at it's | |
* simplest, a mechanism for ensuring that all the code your app needs, and no more, | |
* is bundled and sent to the browser. Though class dependencies are usually specified | |
* by directly referring to other classes, there are times when this is | |
* inappropriate:</p> | |
* | |
* <ol> | |
* <li>We sometimes need a level of indirection, so that dependencies can be expressed | |
* without knowing the concrete class that will end up being used (e.g. services).</li> | |
* <li>It is sometimes useful to specify components declaratively, where it would be confusing | |
* to have to refer to the classes that provide some feature, rather than talking in terms | |
* of the feature itself.</li> | |
* </ol> | |
* | |
* <p>Another useful facet of aliases is that they can be used to automatically | |
* discover all of the classes which implement a particular interface, which | |
* makes it a good candidate for creating SPI type, auto-discovery mechanisms.</p> | |
* | |
* @module br/AliasRegistry | |
*/ | |
var br = require('br/Core'); | |
var Errors = require('./Errors'); | |
/** | |
* @class | |
* @alias module:br/AliasRegistry | |
*/ | |
var AliasRegistry = function() | |
{ | |
this._aliasData = null; | |
this._isAliasDataSet = false; | |
} | |
/** | |
* Returns an array containing the names of all aliases in use within the application. | |
* | |
* @type Array | |
*/ | |
AliasRegistry.prototype.getAllAliases = function getAllAliases() { | |
ensureAliasDataHasBeenSet.call(this); | |
return Object.keys(this._aliasData); | |
}; | |
/** | |
* Returns a filtered subset of the aliases provided by | |
* {@link module:br/AliasRegistry.getAllAliases}. | |
* | |
* <p>An alias is considered to be associated with an interface if the XML configuration for that | |
* alias specifically mentions the given interface, or if the class the alias points to happens to | |
* implement the given interface.</p> | |
* | |
* @param {function} interface the interface being used to filter the aliases by. | |
* @type Array | |
*/ | |
AliasRegistry.prototype.getAliasesByInterface = function getAliasesByInterface(protocol) { | |
ensureAliasDataHasBeenSet.call(this); | |
var allAliases = this.getAllAliases(); | |
var filteredAliases = []; | |
for(var i = 0, length = allAliases.length; i < length; ++i) { | |
var alias = allAliases[i]; | |
var aliasInterface = this._aliasData[alias]["interface"]; | |
if(aliasInterface === protocol) { | |
filteredAliases.push(alias); | |
} else if (this.isAliasAssigned(alias)) { | |
var aliasClass = this.getClass(alias); | |
if(br.classIsA(aliasClass, protocol)) { | |
filteredAliases.push(alias); | |
} | |
} | |
} | |
return filteredAliases; | |
}; | |
/** | |
* Returns a class corresponding to the requested alias name. | |
* | |
* @throws {Errors.IllegalState} if the given alias doesn't exist. | |
* @param {String} aliasName alias name. | |
* @type function | |
*/ | |
AliasRegistry.prototype.getClass = function getClass(aliasName) { | |
ensureAliasDataHasBeenSet.call(this); | |
if (!this.isAliasAssigned(aliasName)) { | |
throw new Errors.IllegalStateError("No class has been found for alias '" + aliasName +"'"); | |
} | |
return this._aliasData[aliasName]["class"]; | |
}; | |
/** | |
* Returns whether the given alias is defined. | |
* | |
* @param {String} aliasName alias name. | |
* @type boolean | |
*/ | |
AliasRegistry.prototype.isAlias = function isAlias(aliasName) { | |
ensureAliasDataHasBeenSet.call(this); | |
return aliasName in this._aliasData; | |
}; | |
/** | |
* Returns whether the given alias has been assigned a value — i.e. whether an alias has a | |
* class value. | |
* | |
* @param {String} aliasName alias name. | |
* @type boolean | |
*/ | |
AliasRegistry.prototype.isAliasAssigned = function isAliasAssigned(aliasName) { | |
ensureAliasDataHasBeenSet.call(this); | |
return this.isAlias(aliasName) && this._aliasData[aliasName]["class"] !== undefined; | |
}; | |
/** | |
* Sets the alias data. | |
* | |
* If the alias data is inconsistent, this will throw Errors. | |
*/ | |
AliasRegistry.prototype.setAliasData = function setAliasData(unverifiedAliasData) { | |
if (this._isAliasDataSet === true) { | |
throw new Errors.IllegalStateError("Alias data has already been set; unable to set again."); | |
} | |
this._isAliasDataSet = true; | |
this._aliasData = unverifiedAliasData; | |
var aliases = this.getAllAliases(); | |
var incorrectAliases = []; | |
var i; | |
for (i = 0; i < aliases.length; ++i) { | |
var aliasId = aliases[i]; | |
var alias = this._aliasData[aliasId]; | |
if (this.isAliasAssigned(aliasId) && alias["interface"]) { | |
var aliasClass = alias["class"]; | |
var protocol = alias["interface"]; | |
if (br.classIsA(aliasClass, protocol) == false) { | |
incorrectAliases.push(aliasId); | |
} | |
} | |
} | |
if(incorrectAliases.length > 0) { | |
var errorMessage = 'The classes for the following aliases do not implement their required interfaces: \n'; | |
for(i = 0; i < incorrectAliases.length; ++i) | |
{ | |
var incorrectAlias = incorrectAliases[i]; | |
errorMessage += '[' + incorrectAlias + ']: "' + this._aliasData[incorrectAlias]["className"] + '" should implement "' + this._aliasData[incorrectAlias].interfaceName + '";\n'; | |
} | |
this._isAliasDataSet = false; | |
this._aliasData = null; | |
throw new Errors.IllegalStateError(errorMessage); | |
} | |
}; | |
/** | |
* @private | |
*/ | |
function ensureAliasDataHasBeenSet() { | |
if (this._isAliasDataSet !== true) { | |
throw new Errors.IllegalStateError("Alias data has not been set."); | |
} | |
} | |
module.exports = new AliasRegistry(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/** | |
* The <code>ServiceRegistry</code> is a static class and does not need to be constructed. | |
* | |
* The <code>ServiceRegistry</code> is used to allow a given application access to application | |
* services. | |
* | |
* <p>Services are typically registered or requested using an alias name, but older applications | |
* may still register and request using interfaces, which is also still supported. Applications | |
* that use aliases don't normally need to manually register services as these are created lazily | |
* upon first request, but will still need to manually register services that can't be created | |
* using a zero-arg constructor.</p> | |
* | |
* <p>The <code>ServiceRegistry</code> is initialized as follows:</p> | |
* | |
* <ol> | |
* <li>The application invokes {@link br.ServiceRegistry.initializeServices} which | |
* causes all delayed readiness services to be created.</li> | |
* <li>Once {@link br.ServiceRegistry.initializeServices} has finished (once one of the | |
* call-backs fire), the application should then register any services that can't be created | |
* lazily using zero-arg constructors.</li> | |
* <li>The application can now start doing it's proper work.</li> | |
* </ol> | |
* | |
* <p>Because blades aren't allowed to depend directly on classes in other blades, interface | |
* definitions are instead created for particular pieces of functionality, and blades can choose | |
* to register themselves as being providers of that functionality. The | |
* <code>ServiceRegistry</code> and the {@link br.EventHub} are both useful in this | |
* regard: | |
* | |
* <ul> | |
* <li>Many-To-One dependencies are resolved by having a single service instance available via | |
* the <code>ServiceRegistry</code>.</li> | |
* <li>Many-To-Many dependencies are resolved by having zero or more classes register with the | |
* {@link br.EventHub}.</li> | |
* </ul> | |
* | |
* @name br.ServiceRegistry | |
* @class | |
* @module br/ServiceRegistry | |
* @see {@link http://bladerunnerjs.org/docs/concepts/service_registry/} | |
* @see {@link http://bladerunnerjs.org/docs/use/service_registry/} | |
*/ | |
var Errors = require('./Errors'); | |
var AliasRegistry = require('./AliasRegistry'); | |
var registry = {}; | |
// Main API ////////////////////////////////////////////////////////////////////////////////////// | |
/** | |
* Register an object that will be responsible for implementing the given interface within the | |
* application. | |
* | |
* @param {String} identifier The alias used to uniquely identify the service. | |
* @param {Object} serviceInstance The object responsible for providing the service. | |
* @throws {Error} If a service has already been registered for the given interface or if no | |
* instance object is provided. | |
*/ | |
function registerService(alias, serviceInstance) { | |
if (serviceInstance === undefined) { | |
throw new Errors.InvalidParametersError("The service instance is undefined."); | |
} | |
if (alias in registry) { | |
throw new Errors.IllegalStateError("Service: " + alias + " has already been registered."); | |
} | |
registry[alias] = serviceInstance; | |
} | |
exports.registerService = registerService; | |
/** | |
* De-register a service that is currently registered in the <code>ServiceRegistry</code>. | |
* | |
* @param {String} sIdentifier The alias or interface name used to uniquely identify the service. | |
*/ | |
function deregisterService(alias) { | |
delete registry[alias]; | |
} | |
exports.deregisterService = deregisterService; | |
/** | |
* Retrieve the service linked to the identifier within the application. The identifier could be a | |
* service alias or a service interface. | |
* | |
* @param {String} identifier The alias or interface name used to uniquely identify the service. | |
* @throws {Error} If no service could be found for the given identifier. | |
* @type Object | |
*/ | |
function getService(alias) { | |
initializeServiceIfRequired(alias); | |
if (registry[alias] === undefined){ | |
throw new Errors.InvalidParametersError("br/ServiceRegistry could not locate a service for: " + alias); | |
} | |
return registry[alias]; | |
} | |
exports.getService = getService; | |
/** | |
* Determine whether a service has been registered for a given identifier. | |
* | |
* @param {String} identifier The alias or interface name used to uniquely identify the service. | |
* @type boolean | |
*/ | |
function isServiceRegistered(alias) { | |
return alias in registry; | |
} | |
exports.isServiceRegistered = isServiceRegistered; | |
/** | |
* Resets the <code>ServiceRegistry</code> back to its initial state. | |
* | |
* <p>This method isn't normally called within an application, but is called automatically before | |
* each test is run.</p> | |
*/ | |
function clear() { | |
registry = {}; | |
} | |
exports.clear = clear; | |
// private statics /////////////////////////////////////////////////////////////////////////////// | |
/** @private */ | |
function initializeServiceIfRequired(alias) { | |
if (alias in registry === false) { | |
var isIdentifierAlias = AliasRegistry.isAliasAssigned(alias); | |
if (isIdentifierAlias) { | |
var ServiceClass = AliasRegistry.getClass(alias); | |
registry[alias] = new ServiceClass(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment