From ea4619c3d849fb2a9845cde4b493fbdb4a0f1f13 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 5 Nov 2014 17:25:10 -0800 Subject: [PATCH] [Framework] Document implementation Document implementation more fully, including notes on composite services. WTD-518. --- platform/framework/README.md | 123 ++++++++++++++++++ .../src/register/ExtensionRegistrar.js | 4 +- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/platform/framework/README.md b/platform/framework/README.md index 8fbe80d58d..45a12d5e21 100644 --- a/platform/framework/README.md +++ b/platform/framework/README.md @@ -2,6 +2,28 @@ Framework-level components for Open MCT Web. This is Angular and Require, with an extra layer to mediate between them and act as an extension mechanism to allow plug-ins to be introduced declaratively. +# Usage + +This section needs to be written. For now, refer to implementation notes and +examples in `example/builtins`, `example/extensions`, and `example/composite`. + +## Circular dependencies + +The framework layer (like Angular itself) does not support circular +dependencies among extensions. Generally, circular dependencies can be +avoided by refactoring; for instance, a dependency-less intermediary can be +added by two parties which depend upon one another, and both can depend upon +this intermediary while one abandons its dependency to the other (the +intermediary must then provide the functionality that was needed in the +abandoned dependency.) + +In some cases this refactoring is non-obvious or ineffective (for instance, +when a service component depends upon the whole.) In these cases, Angular's +`$injector` may be used to break the declaration-time dependency, by allowing +retrieval of the dependency at use-time instead. (This is essentially the +same solution as above, where `$injector` acts as an application-global +generalized intermediary.) + # Implementation Notes The framework layer is responsible for performing a four-stage initialization @@ -21,3 +43,104 @@ process. These stages are: 4. __Bootstrapping.__ JSON declarations are loaded for all bundles which will constitute the application, and wrapped in a useful API for subsequent stages. _Sources in `src/bootstrap`_ + +Additionally, the framework layer takes responsibility for initializing +other application state. Currently this simply means adding Promise to the +global namespace if it is not defined. + +## Load stage + +Using Angular's `$http`, the list of installed bundles is loaded from +`bundles.json`; then, each bundle's declaration (its path + `bundle.json`) +is loaded. These are wrapped by `Bundle` objects, and the extensions they +expose are wrapped by `Extension` objects; this is only to provide a +useful API for subsequent stages. + +A bundle is a set of related extensions; an extension is an individual +unit of the application that is meant to be used by other pieces of the +application. + +## Resolution stage + +Some, but not all, individual extensions have corresponding scripts. +These are referred to by the `implementation` field in their extension +definition. The implementation name should not include the bundle path, +or the name of the source folder; these will be pre-pended by the framework +during this stage. The implementation name should include a `.js` extension. + +An extension is resolved by loading its implementing script, if one has been +declared. If none is declared, the extension's raw definition is used +instead. To ensure that extensions look similar regardless of whether or +not an implementation is present, all key-value pairs from the definition +are copied to the loaded implementation (if one has been loaded.) + +## Registration stage + +Following implementation resolution, extensions are registered by Angular. +How this registration occurs depends on whether or not there is built in +support for the category of extension being registered. + +* For _built-in_ extension types (recognized by Angular), these are + registered with the application module. These categories are `directives`, + `controllers`, `services`, and `routes`. +* For _composite services_, extensions of category `components` are passed + to the service compositor, which builds up a dependency graph among + the components such that their fully-wired whole is exposed as a single + service. +* For _general extensions_, the resolved extensions are assembled into a + list, with Angular-level dependencies are declared, and the full set + is exposed as a single Angular "service." + +### Composite services + +Composite services are assumed to follow a provider-aggregator-decorator +pattern where: + +* _Providers_ have dependencies as usual, and expose the API associated + with the service they compose. Providers are full service implementations + in-and-of-themselves. +* _Aggregators_ have dependencies as usual plus one additional dependency, + which will be satisfied by the array of all providers registered of + that type of service. Implementations are assumed to include an extra + argument (after what they declare in `depends`) to receive this array. + Aggregators make multiple providers appear as one. +* _Decorators_ have dependencies as usual plus one additional dependency, + which will be satisfied by either an aggregator (if one is present), + the latest provider (if no aggregator is present), or another decorator + (if multiple decorators are present.) As with aggregators, an additional + argument should be accepted by the implementation to receive this. + Decorators modify or augment the behavior of a service, but do not + provide its core functionality. +* All of the above must be declared with a `provides` property, which + indicates which type of service they compose. Providers will only be + paired with aggregators of matching types, and so on. The value of + this property is also the name of the service that is ultimately + registered with Angular to represent the composite service as a whole. + +The service compositor handles this in five steps: + +1. All providers are registered. +2. Arrays of providers are registered. +3. All aggregators are registered (with dependencies to the arrays + registered in the previous step.) +4. All decorators are registered (with dependencies on the most recent + components of matching types.) +5. Full composite services are registered (essentially aliasing back + to the latest component registered of a given type.) + +Throughout these steps, components are registered with Angular using +generated names like `typeService[decorator#11]`. It is technically possible +to reference these dependencies elsewhere but that is not the intent. +Rather, the resulting composed service should be referred to as +`typeService` (or, more generally, the value matched from the `provides` +field of the paired service components.) + +### General extensions + +Similar to composite services, each individual general extension gets +registered using a generated name, like `types[extension#0]`. These are +not intended to be referenced directly; instead, they are declared +dependencies of the full list of general extensions of a given category. +This list of extensions is registered with a square-brackets suffix, +like `types[]`; this _is_ intended to be declared as a dependency by +non-framework code. \ No newline at end of file diff --git a/platform/framework/src/register/ExtensionRegistrar.js b/platform/framework/src/register/ExtensionRegistrar.js index 69464c2d47..c812a1249a 100644 --- a/platform/framework/src/register/ExtensionRegistrar.js +++ b/platform/framework/src/register/ExtensionRegistrar.js @@ -29,8 +29,8 @@ define( // so that these can be registered separately with Angular function identify(category, extension, index) { var name = extension.key ? - (extension.key + "-" + index) : - index; + ("extension-" + extension.key + "#" + index) : + ("extension#" + index); return category + "[" + name + "]"; }