4.6 KiB
4.6 KiB
Imperative Plugins
This is a design proposal for handling bundle declarations in JavaScript.
Developer Use Cases
Developers will want to use bundles/plugins to (in rough order of occurrence):
- Add new extension instances.
- Use existing services
- Add new service implementations.
- Decorate service implementations.
- Decorate extension instances.
- Add new types of services.
- Add new extension categories.
Notably, bullets 4 and 5 above are currently handled implicitly, which has been cited as a source of confusion.
Interfaces
Two base classes may be used to satisfy these use cases:
- The
CompositeServiceFactoryprovides composite service instances. Decorators may be added; the approach used for compositing may be modified; and individual services may be registered to support compositing. - The
ExtensionRegistryallows 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
more general-purpose Registry
[Factory.<T, V>
|
- factoryFn : function (V) : T
|
+ decorate(decoratorFn : function (T, V) : T, options? : RegistrationOptions)
]-:>[function (V) : T]
[RegistrationOptions |
+ priority : number or string
]
[Registry.<T, V>
|
- compositorFn : function (Array.<T>) : V
|
+ register(item : T, options? : RegistrationOptions)
+ composite(compositorFn : function (Array.<T>) : V, options? : RegistrationOptions)
]-:>[Factory.<V, Void>]
[Factory.<V, Void>]-:>[Factory.<T, V>]
[ExtensionRegistry.<T>]-:>[Registry.<T, Array.<T>>]
[Registry.<T, Array.<T>>]-:>[Registry.<T, V>]
[CompositeServiceFactory.<T>]-:>[Registry.<T, T>]
[Registry.<T, T>]-:>[Registry.<T, V>]
Examples
1. Add new extension instances.
// Instance-style registration
mct.types.register(new mct.Type({
key: "timeline",
name: "Timeline",
description: "A container for activities ordered in time."
});
// Factory-style registration
mct.actions.register(function (domainObject) {
return new RemoveAction(domainObject);
}, { priority: 200 });
2. Use existing services
mct.actions.register(function (domainObject) {
var dialogService = mct.ui.dialogServiceFactory();
return new PropertiesAction(dialogService, domainObject);
});
3. Add new service implementations
// 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
mct.modelServiceFactory.decorate(function (modelService) {
return new CachingModelDecorator(modelService);
}, { priority: 100 });
5. Decorate extension instances
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
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.
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.
- Relies on factories, increasing number of interfaces to be concerned with.