Update description of imperative plugins

This commit is contained in:
Victor Woeltjen 2015-12-22 12:29:57 -08:00
parent 4579b4fabc
commit c99b6df9fc

View File

@ -10,87 +10,155 @@ Developers will want to use bundles/plugins to (in rough order
of occurrence): of occurrence):
1. Add new extension instances. 1. Add new extension instances.
2. Add new service implementations. 2. Use existing services
3. Decorate service implementations. 3. Add new service implementations.
4. Add new types of services. 4. Decorate service implementations.
5. Add new extension categories. 5. Decorate extension instances.
6. Add new types of services.
7. Add new extension categories.
Notably, bullets 4 and 5 above are currently handled implicitly, Notably, bullets 4 and 5 above are currently handled implicitly,
which has been cited as a source of confusion. which has been cited as a source of confusion.
## Interfaces ## Interfaces
```javascript Two base classes may be used to satisfy these use cases:
/** * The `CompositeServiceFactory` provides composite service instances.
* Something that can be installed in a running instance of MCT. Decorators may be added; the approach used for compositing may be
* @interface Installable modified; and individual services may be registered to support compositing.
*/ * The `ExtensionRegistry` allows for the simpler case where what is desired
is an array of all instances of some kind of thing within the system.
/** Note that additional developer use cases may be supported by using the
* Install this plugin in an instance of MCT. more general-purpose `Registry`
* @method Installable#install
* @param mct the instance of MCT in which to install
*/
/** ```nomnoml
* A bundle is a set of related features that can [Function.<T, V>]
* be installed in MCT.
* @class
* @implements {Installable}
* @param {Metadata} metadata metadata about this bundle
*/
function Bundle(metadata) {
this.metadata = metadata;
}
Bundle.prototype.service [Factory.<T, V>
Bundle.prototype.install = function (mct) {}; |
- factoryFn : Function.<T, V>
|
+ decorate(decoratorFn : Function.<T, T>, options? : RegistrationOptions)
]-:>[Function.<T, V>]
/** [RegistrationOptions |
* Data about a given entity within the system. + priority : number or string
* @typedef Metadata
* @property {string} name the human-readable name of the entity
* @property {string} key the machine-readable identifier for the entity
* @property {string} description a human-readable summary of the entity
*/
[Dependency<T> |
get() : T
] ]
[Registry<T> | [Registry.<T, V>
register( |
dependencies : Dependency[], - compositorFn : Function.<V, Array.<T>>
factory : Function<T> |
) + register(item : T, options? : RegistrationOptions)
] + composite(compositorFn : Function.<V, Array.<T>>, options? : RegistrationOptions)
]-:>[Factory.<V, Void>]
[Factory.<V, Void>]-:>[Factory.<T, V>]
[ExtensionRegistry<T> | [ExtensionRegistry.<T>]-:>[Registry.<T, Array.<T>>]
] [Registry.<T, Array.<T>>]-:>[Registry.<T, V>]
[ExtensionRegistry]
[CompositeServiceFactory.<T>]-:>[Registry.<T, T>]
[Registry.<T, T>]-:>[Registry.<T, V>]
``` ```
Creating a bundle then looks like: ## Examples
```javascript ### 1. Add new extension instances.
define([
'mct', ```js
'./SomeExtension', // Instance-style registration
'./SomeService' mct.types.register(new mct.Type({
], function (mct, SomeExtension, SomeService) { key: "timeline",
var plugin = new mct.Bundle({ name: "Timeline",
key: 'myBundle', description: "A container for activities ordered in time."
name: "My bundle",
description: "A bundle that I made."
});
plugin.extension
return plugin;
}); });
// Factory-style registration
mct.actions.register(function (domainObject) {
return new RemoveAction(domainObject);
}, { priority: 200 });
``` ```
### 2. Use existing services
```js
mct.actions.register(function (domainObject) {
var dialogService = mct.ui.dialogServiceFactory();
return new PropertiesAction(dialogService, domainObject);
});
```
### 3. Add new service implementations
```js
// Instance-style registration
mct.persistenceServiceFactory.register(new LocalPersistenceService());
// Factory-style registration
mct.persistenceServiceFactory.register(function () {
var $http = angular.injector(['ng']).get('$http');
return new LocalPersistenceService($http);
});
```
### 4. Decorate service implementations
```js
mct.modelServiceFactory.decorate(function (modelService) {
return new CachingModelDecorator(modelService);
}, { priority: 100 });
```
### 5. Decorate extension instances
```js
mct.capabilities.decorate(function (capabilities) {
return capabilities.map(decorateIfApplicable);
});
```
This use case is not well-supported by these API changes. The most
common case for decoration is capabilities, which are under reconsideration;
should consider handling decoration of capabilities in a different way.
### 6. Add new types of services
```js
myModule.myServiceFactory = new mct.CompositeServiceFactory();
// In cases where a custom composition strategy is desired
myModule.myServiceFactory.composite(function (services) {
return new MyServiceCompositor(services);
});
```
### 7. Add new extension categories.
```js
myModule.hamburgers = new mct.ExtensionRegistry();
```
## Evaluation
### Benefits
* Encourages separation of registration from declaration (individual
components are decoupled from the manner in which they are added
to the architecture.)
* Minimizes "magic." Dependencies are acquired, managed, and exposed
using plain-old-JavaScript without any dependency injector present
to obfuscate what is happening.
* Offers comparable expressive power to existing APIs; can still
extend the behavior of platform components in a variety of ways.
* Does not force or limit formalisms to use;
### Detriments
* Does not encourage separation of dependency acquisition from
declaration; that is, it would be quite natural using this API
to acquire references to services during the constructor call
to an extension or service. But, passing these in as constructor
arguments is preferred (to separate implementation from architecture.)
* Adds (negligible?) boilerplate relative to declarative syntax.