diff --git a/docs/src/design/index.md b/docs/src/design/index.md new file mode 100644 index 0000000000..7b4c3e4ebf --- /dev/null +++ b/docs/src/design/index.md @@ -0,0 +1,3 @@ +Design proposals: + +* [API Redesign](proposals/APIRedesign.md) \ No newline at end of file diff --git a/docs/src/design/planning/APIRefactor.md b/docs/src/design/planning/APIRefactor.md new file mode 100644 index 0000000000..8f693cb814 --- /dev/null +++ b/docs/src/design/planning/APIRefactor.md @@ -0,0 +1,338 @@ +# API Refactoring + +This document summarizes a path toward implementing API changes +from the [API Redesign](../proposals/APIRedesign.md) for Open MCT Web +v1.0.0. + +# Goals + +These plans are intended to minimize: + +* Waste; avoid allocating effort to temporary changes. +* Downtime; avoid making changes in large increments that blocks + delivery of new features for substantial periods of time. +* Risk; ensure that changes can be validated quickly, avoid putting + large effort into changes that have not been validated. + +# Plan + +```nomnoml +#comment: This diagram is in nomnoml syntax and should be rendered. +#comment: See https://github.com/nasa/openmctweb/issues/264#issuecomment-167166471 + + +[ Start]->[ Imperative bundle registration] + +[ Imperative bundle registration]->[ Build and packaging] +[ Imperative bundle registration]->[ Refactor API] + +[ Build and packaging | + [ Start]->[ Incorporate a build step] + [ Incorporate a build step | + [ Start]->[ Choose package manager] + [ Start]->[ Choose build system] + [ Choose build system]<->[ Choose package manager] + [ Choose package manager]->[ Implement] + [ Choose build system]->[ Implement] + [ Implement]->[ End] + ]->[ Separate repositories] + [ Separate repositories]->[ End] +]->[ Release candidacy] + + +[ Start]->[ Design registration API] + +[ Design registration API | + [ Start]->[ Decide on role of Angular] + [ Decide on role of Angular]->[ Design API] + [ Design API]->[ Passes review?] + [ Passes review?] no ->[ Design API] + [ Passes review?]-> yes [ End] +]->[ Refactor API] + +[ Refactor API | + [ Start]->[ Imperative extension registration] + [ Imperative extension registration]->[ Refactor individual extensions] + + [ Refactor individual extensions | + [ Start]->[ Prioritize] + [ Prioritize]->[ Sufficient value added?] + [ Sufficient value added?] no ->[ End] + [ Sufficient value added?] yes ->[ Design] + [ Design]->[ Passes review?] + [ Passes review?] no ->[ Design] + [ Passes review?]-> yes [ Implement] + [ Implement]->[ End] + ]->[ Remove legacy bundle support] + + [ Remove legacy bundle support]->[ End] +]->[ Release candidacy] + +[ Release candidacy | + [ Start]->[ Verify | + [ Start]->[ API well-documented?] + [ Start]->[ API well-tested?] + [ API well-documented?]-> no [ Write documentation] + [ API well-documented?] yes ->[ End] + [ Write documentation]->[ API well-documented?] + [ API well-tested?]-> no [ Write test cases] + [ API well-tested?]-> yes [ End] + [ Write test cases]->[ API well-tested?] + ] + [ Start]->[ Validate | + [ Start]->[ Passes review?] + [ Start]->[ Use internally] + [ Use internally]->[ Proves useful?] + [ Passes review?]-> no [ Address feedback] + [ Address feedback]->[ Passes review?] + [ Passes review?] yes -> [ End] + [ Proves useful?] yes -> [ End] + [ Proves useful?] no -> [ Fix problems] + [ Fix problems]->[ Use internally] + ] + [ Validate]->[ End] + [ Verify]->[ End] +]->[ Release] + +[ Release]->[ End] +``` + +## Step 1. Imperative bundle registration + +Register whole bundles imperatively, using their current format. + +For example, in each bundle add a `bundle.js` file: + +```js +define([ + 'mctRegistry', + 'json!bundle.json' +], function (mctRegistry, bundle) { + mctRegistry.install(bundle, "path/to/bundle"); +}); +``` + +Where `mctRegistry.install` is placeholder API that wires into the +existing bundle registration mechanisms. The main point of entry +would need to be adapted to clearly depend on these bundles +(in the require sense of a dependency), and the framework layer +would need to implement and integrate with this transitional +API. + +Benefits: + +* Achieves an API Redesign goal with minimal immediate effort. +* Conversion to an imperative syntax may be trivially automated. +* Minimal change; reuse existing bundle definitions, primarily. +* Allows early validation of switch to imperative; unforeseen + consequences of the change may be detected at this point. +* Allows implementation effort to progress in parallel with decisions + about API changes, including fundamental ones such as the role of + Angular. May act in some sense as a prototype to inform those + decisions. +* Creates a location (framework layer) where subsequent changes to + the manner in which extensions are registered may be centralized. + When there is a one-to-one correspondence between the existing + form of an extension and its post-refactor form, adapters can be + written here to defer the task of making changes ubiquitously + throughout bundles, allowing for earlier validation and + verification of those changes, and avoiding ubiquitous changes + which might require us to go dark. (Mitigates + ["greenfield paradox"](http://stepaheadsoftware.blogspot.com/2012/09/greenfield-or-refactor-legacy-code-base.html); + want to add value with new API but don't want to discard value + of tested/proven legacy codebase.) + +Detriments: + +* Requires transitional API to be implemented/supported; this is + waste. May mitigate this by time-bounding the effort put into + this step to ensure that waste is minimal. + +Note that API changes at this point do not meaningfully reflect +the desired 1.0.0 API, so no API reviews are necessary. + +## Step 2. Incorporate a build step + +After the previous step is completed, there should be a +straightforward dependency graph among AMD modules, and an +imperative (albeit transitional) API allowing for other plugins +to register themselves. This should allow for a build step to +be included in a straightforward fashion. + +Some goals for this build step: + +* Compile (and, preferably, optimize/minify) Open MCT Web + sources into a single `.js` file. + * It is desirable to do the same for HTML sources, but + may wish to defer this until a subsequent refactoring + step if appropriate. +* Provide non-code assets in a format that can be reused by + derivative projects in a straightforward fashion. + +Should also consider which dependency/packaging manager should +be used by dependent projects to obtain Open MCT Web. Approaches +include: + +1. Plain `npm`. Dependents then declare their dependency with + `npm` and utilize built sources and assets in a documented + fashion. (Note that there are + [documented challenges](http://blog.npmjs.org/post/101775448305/npm-and-front-end-packaging) + in using `npm` in this fashion.) +2. Build with `npm`, but recommend dependents install using + `bower`, as this is intended for front-end development. This may + require checking in built products, however, which + we wish to avoid (this could be solved by maintaining + a separate repository for built products.) + +In all cases, there is a related question of which build system +to use for asset generation/management and compilation/minification/etc. + +1. [`webpack`](https://webpack.github.io/) + is well-suited in principle, as it is specifically + designed for modules with non-JS dependencies. However, + there may be limitations and/or undesired behavior here + (for instance, CSS dependencies get in-lined as style tags, + removing our ability to control ordering) so it may +2. `gulp` or `grunt`. Commonplace, but both still require + non-trivial coding and/or configuration in order to produce + appropriate build artifacts. +3. [Just `npm`](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/). + Reduces the amount of tooling being used, but may introduce + some complexity (e.g. custom scripts) to the build process, + and may reduce portability. + +## Step 3. Separate repositories + +Refactor existing applications built on Open MCT Web such that they +are no longer forks, but instead separate projects with a dependency +on the built artifacts from Step 2. + +Note that this is achievable already using `bower` (see `warp-bower` +branch at http://developer.nasa.gov/mct/warp for an example.) +However, changes involved in switching to an imperative API and +introducing a build process may change (and should simplify) the +approach used to utilize Open MCT Web as a dependency, so these +changes should be introduced first. + +## Step 4. Design registration API + +Design the registration API that will replace declarative extension +categories and extensions (including Angular built-ins and composite +services.) + +This may occur in parallel with implementation steps. + +It will be necessary +to have a decision about the role of Angular at this point; are extensions +registered via provider configuration (Angular), or directly in some +exposed registry? + +Success criteria here should be based on peer review. Scope of peer +review should be based on perceived risk/uncertainty surrounding +proposed changes, to avoid waste; may wish to limit this review to +the internal team. (The extent to which external +feedback is available is limited, but there is an inherent timeliness +to external review; need to balance this.) + +Benefits: + +* Solves the "general case" early, allowing for early validation. + +Note that in specific cases, it may be desirable to refactor some +current "extension category" in a manner that will not appear as +registries, _or_ to locate these in different +namespaces, _or_ to remove/replace certain categories entirely. +This work is deferred intentionally to allow for a solution of the +general case. + +## Step 5. Imperative extension registration + +Register individual extensions imperatively, implementing API changes +from the previous step. At this stage, _usage_ of the API may be confined +to a transitional adapter in the framework layer; bundles may continue +to utilize the transitional API for registering extensions in the +legacy format. + +An important, ongoing sub-task here will be to discover and define dependencies +among bundles. Composite services and extension categories are presently +"implicit"; after the API redesign, these will become "explicit", insofar +as some specific component will be responsible for creating any registries. +As such, "bundles" which _use_ specific registries will need to have an +enforceable dependency (e.g. require) upon those "bundles" which +_declare_ those registries. + +## Step 6. Refactor individual extensions + +Refactor individual extension categories and/or services that have +been identified as needing changes. This includes, but is not +necessarily limited to: + +* Views/Representations/Templates (refactored into "components.") +* Capabilities (refactored into "roles", potentially.) +* Telemetry (from `TelemetrySeries` to `TelemetryService`.) + +Changes should be made one category at a time (either serially +or separately in parallel) and should involve a tight cycle of: + +1. Prioritization/reprioritization; highest-value API improvements + should be done first. +2. Design. +3. Review. Refactoring individual extensions will require significant + effort (likely the most significant effort in the process) so changes + should be validated early to minimize risk/waste. +4. Implementation. These changes will not have a one-to-one relationship + with existing extensions, so changes cannot be centralized; usages + will need to be updated across all "bundles" instead of centralized + in a legacy adapter. If changes are of sufficient complexity, some + planning should be done to spread out the changes incrementally. + +By necessity, these changes may break functionality in applications +built using Open MCT Web. On a case-by-case basis, should consider +providing temporary "legacy support" to allow downstream updates +to occur as a separate task; the relevant trade here is between +waste/effort required to maintain legacy support, versus the +downtime which may be introduced by making these changes simultaneously +across several repositories. + + +## Step 7. Remove legacy bundle support + +Update bundles to remove any usages of legacy support for bundles +(including that used by dependent projects.) Then, remove legacy +support from Open MCT Web. + +## Step 8. Release candidacy + +Once API changes are complete, Open MCT Web should enter a release +candidacy cycle. Important things to look at here: + +* Are changes really complete? + * Are they sufficiently documented? + * Are they sufficiently tested? +* Are changes really sufficient? + * Do reviewers think they are usable? + * Does the development team find them useful in practice? This + will require calendar time to ascertain; should allocate time + for this, particularly in alignment with the sprint/release + cycle. + * Has learning curve been measurably decreased? Comparing a to-do + list tutorial to [other examples(http://todomvc.com/) could + provide an empirical basis to this. How much code is required? + How much explanation is required? How many dependencies must + be installed before initial setup? + * Does the API offer sufficient power to implement the extensions we + anticipate? + * Any open API-related issues which should block a 1.0.0 release? + +Any problems identified during release candidacy will require +subsequent design changes and planning. + +## Step 9. Release + +Once API changes have been verified and validated, proceed +with release, including: + +* Tagging as version 1.0.0 (at an appropriate time in the + sprint/release cycle.) +* Close any open issues which have been resolved (or made obsolete) + by API changes. \ No newline at end of file diff --git a/docs/src/design/proposals/APIRedesign.md b/docs/src/design/proposals/APIRedesign.md new file mode 100644 index 0000000000..479460b457 --- /dev/null +++ b/docs/src/design/proposals/APIRedesign.md @@ -0,0 +1,1282 @@ +# Overview + +The purpose of this document is to review feedback on Open MCT Web's +current API and propose improvements to the API, particularly for a +1.0.0 release. + +Strategically, this is handled by: + +* Identifying broader goals. +* Documenting feedback and related background information. +* Reviewing feedback to identify trends and useful features. + * In particular, pull out "pain points" to attempt to address, + as well as positive attributes to attempt to preserve. +* Proposing a set of API changes to address these "pain points." + * This also takes into account scheduling concerns. +* Once agreed-upon, formalize this set of changes (e.g. as UML + diagrams) and plan to implement them. + +# Goals + +## Characteristics of a good API + +A good API: + +* Is easy to understand. +* Rewards doing things "the right way." +* Saves development effort. +* Is powerful enough to support a broad range of applications. +* Lends itself to good documentation. + +These characteristics can sometimes be at odds with each other, or +with other concerns. These should typically be viewed as participants +in trades. + +## Evaluating APIs + +APIs may be evaluated based on: + +* Number of interfaces. + * How many application-specific interfaces do I need to know to + solve a certain class of problems? +* Size of interfaces. + * How many methods does each interface have? +* Depth of interfaces. + * Specifically, how many methods do I need to call before the return + value is of a form that is not specific to the API? +* Clarity of interfaces. + * How much documentation or learning is required before an interface is + useful? +* Consistency of interfaces. + * How similar is one interface to an analogous interface? +* Utility of interfaces. + * How much development effort is reduced by utilizing these interfaces, + versus accomplishing the same goals with other tools? +* Power of interfaces. + * How much application functionality can I influence with the interfaces + that are available to me? + +In general, prefer to have a small number of simple, shallow, clear, +useful, powerful interfaces. + +# Developer Feedback + +## Developer Intern Feedback + +This feedback comes from interns who worked closely with +Open MCT Web as their primary task over the Summer of 2015. + +### Developer Intern 1 + +Worked on bug fixes in the platform and a plugin for search. + +* Initially, it was confusing that many things in files that are in + very different locations in the code base refer to each other. + * Perhaps explain more the organization strategy behind the + different main sections, like "commonUI" vs "core". +* This may be just me, but there are often long chains of related + functions calling each other, and when I had to modify the behavior, + I had a hard time remembering to look for the highest level function + in the call chain to change. I also sometimes had a hard time finding + the connections between the functions. But, that is important because + the implementation of the functions along the chain may change later. +* One very helpful thing that you could add might just be documentation + that is not in paragraph format like in the current developer guide. + I would just like a list of all the functions and members of each kind + of object there is, and descriptions of what they are and how they're + used. + * Also, the current developer guide pdf's words that are in 'code font', + rather than the normal text, are not searchable. + (Depending on the pdf viewer.) +* I do appreciate that there is some example code. +* I am still slightly confused about what "domainObject" refers to in + different situations. +* The tutorials are helpful, but only really for designing new views. + It doesn't help much with gaining understanding of how the other parts + of the application work. +* The general idea of 'telemetry' in this context is kind of confusing. + It is hard to figure out what the difference between the various ways of + dealing with telemetry are. e.g., what is the difference between just + "Telemetry" and the "Telemetry Service"? There are many + "Telemetry Thing"s which seem related, but in an unclear way. + +### Developer Intern 2 + +Worked on platform bug fixes and mobile support. + +* No guide for the UI and front end for the HTML/CSS part of Open MCT Web. + Not sure if this is applicable or needed for developers, however would + be helpful to any front end development +* Found it difficult to follow the plot controller & subplot + functions/features, such as zooming. +* If the developer guide could have for references to which files or + functions are key for gestures, browse navigation, etc it would be + helpful for future developers as a place to start looking. I found + it occasionally difficult to find which files or functions I wanted + at first. + +## Plugin Developer Feedback + +This feedback comes from developers who have worked on plugins for +Open MCT Web, but have not worked on the platform. + +### Plugin Developer 1 + +Used Open MCT Web over the course of several months (on a +less-than-half-time basis) to develop a +spectrum visualization plugin. + +* Not a lot of time to work on this, made it hard to get up the learning + curve. + * Note that this is the norm, particularly for GDS development. +* JavaScript carries its own learning curve. +* The fact that it pulls in other tools whose APIs need to be learned + also makes the learning curve harder to get up. +* Tracking down interconnected parts was a bit difficult. +* Could really use examples. +* Easy to get lost when not immersed in the style. + +### Plugin Developer 2 + +Used Open MCT Web over the course of several weeks (on a half-time basis) +to develop a tabular visualization plugin. + +* Pain points + * Unable to copy and paste from tutorial pdfs into code + * Wanted to verify my environment was setup properly so that I + could get the final product working in the end without having + to type everything out. Perhaps there could be something in + github that has the final completed tutorial for new users to + checkout? Or a step by step one kind of like the tutorials on + the angular js webpage? + * Typing too long without seeing results of what I was doing + * At some points in the tutorial I ended up typing for the sake + of typing without knowing what I was really typing for. + * If there were break points where we could run the incomplete + code and just see a variable dump or something even that would + be helpful to know that I am on the right track. + * Documentation on features are a bit hard to find. + * I'm not sure what I can do until I search through examples of + existing code and work my way backwards. + * Maybe you can link the features we are using in the tutorial to + their respective parts in the developer guide? Not sure if that + can be done on PDFs, so maybe a webpage instead? +* Positive Attributes + * Unable to copy and paste from tutorial pdfs into code + * I know I also listed this as a pain, but it was kind of helpful + being forced to read and type everything out. + * "Widgets" are self contained in their own directories. I don't have + to be afraid of exploding things. + * All files/config that I care about for a "widget" can be found in + the bundles.json +* Misc + * Coming from a not so strong webdev background and on top of that a + zero strong angular background I think starting off with a simple + "Hello World" webpage tutorial would have been nice. + * Start off with a bare bones bundle json with an empty controller + and static "Hello World" in the view + * Add the variable "Hello World" into the controller for the view + to display + * Add a model property to the bundle.json to take in "Hello World" + as a parameter and pass through to the controller/view + +### Open Source Contributer + + * [Failures are non-graceful when services are missing.]( + https://github.com/nasa/openmctweb/issues/79) + +## Misc. Feedback (mostly verbal) + +* Easy to add things. +* Separation of concerns is unclear (particularly: "where's the MVC?") +* Telemetry API is confusing. In particular, `TelemetrySeries` should + just be an array. + * Came out of design discussions for Limits. +* Capabilities are confusing. + +## Long-term Developer Notes + +The following notes are from original platform developer, with long +term experience using Open MCT Web. + +* Bundle mechanism allows for grouping related components across concerns, + and adding and removing these easily. (e.g. model and view components of + Edit mode are all grouped together in the Edit bundle.) + +## AngularJS + +Angular 2.0.0 is coming (maybe by end of 2015.) + +It will not be backwards-compatible with Angular 1.x. +The differences are significant enough that switching to +Angular 2 will require only slightly less effort than switching +to an entirely different framework. + +We can expect AngularJS 1.x to reach end-of-life reasonably soon thereafter. + +Our API is currently a superset of Angular's API, so this directly effects +our API. Specifically, API changes should be oriented towards removing +or reducing the Angular dependency. + +### Angular's Role + +Angular is Open MCT Web's: + +* Dependency injection framework. +* Template rendering. +* DOM interactions. +* Services library. +* Form validator. +* Routing. + +This is the problem with frameworks: They become a single point of +failure for unrelated concerns. + +### Rationale for Adopting Angular + +The rationale for adopting AngularJS as a framework is +documented in https://trunk.arc.nasa.gov/jira/browse/WTD-208. +Summary of the expected benefits: + +* Establishes design patterns that are well-documented and + understood in industry. This can be beneficial in training + new staff, and lowers the documentation burden on the local + development team. If MCT-Web were to stay with its current + architecture, significant developer-oriented documentation + and training materials would need to be produced. +* The maintainability of MCT-Web would be enhanced by using a + framework like Angular. The local team would enjoy the benefits of + maintenance performed by the sponsor, but would not incur any cost + for this. This would include future upgrades, testing, and bug fixes. +* Replaces DOM-manipulation with a declarative data-binding syntax + which automatically updates views when the model data changes. This + pattern has the potential to save the development team from + time-consuming and difficult-to-debug DOM manipulation. +* Provides data binding to backend models. +* Provides patterns for form validation. +* Establishes documented patterns for add-on modules and services. +* Supports unit tests and system tests (tests which simulate user + interactions in the browser) +* Angular software releases can be expected to be tested, which would + allow MCT-Web developers to focus on MCT-specific features, instead + of the maintenance of custom infrastructure. + +### Actual Experience with Angular + +Most of the expected benefits of Angular have been invalidated +by experience: + +* Feedback from new developers is that Angular was a hindrance to + training, not a benefit. ("One more thing to learn.") Significant + documentation remains necessary for Open MCT Web. +* Expected enhancements to maintainability will be effectively + invalidated by an expected Angular end-of-life. +* Data binding and automatic view updates do save development effort, + but also carry a performance penalty. This can be solved, but requires + resorting to exactly the sort of DOM manipulations we want to avoid. + In some cases this can require more total development (writing a + poorly-performing Angular version, then "optimizing" by rewriting a + non-Angular version.) +* Expected reduction of test scope will also be invalidated by an + expected end-of-life. + +Other problems: + +* Hinders integrating non-Angular components. (Need to wrap with + Angular API, e.g. as directives, which may be non-trivial.) +* Interferes with debugging by swallowing or obscuring exceptions. + +# Feedback Review + +## Problem Summary + +The following attributes of the current API are undesirable: + +- [ ] It is difficult to tell "where things are" in the code base. +- [ ] It is difficult to see how objects are passed around at run-time. +- [ ] It is difficult to trace flow of control generally. +- [ ] Multiple interfaces for related concepts (e.g. telemetry) is confusing. +- [ ] API documentation is missing or not well-formatted for use. +- [ ] High-level separation of concerns is not made clear. +- [ ] Interface depth of telemetry API is excessive (esp. `TelemetrySeries`) +- [ ] Capabilities as a concept lack clarity. +- [ ] Too many interfaces and concepts to learn. +- [ ] Exposing third-party APIs (e.g. Angular's) increases the learning curve. +- [ ] Want more examples, easier-to-use documentation. +- [ ] UI-relevant features (HTML, CSS) under-documented +- [ ] Good MVC for views of domain objects not enforced (e.g. plots) + +## Positive Features + +It is desirable to retain the following features in an API redesign: + +- [ ] Creating new features and implementing them additively is well-supported. +- [ ] Easy to add/remove features which involve multiple concerns. +- [ ] Features can be self-contained. +- [ ] Declarative syntax makes it easy to tell what's in use. + +## Requirements + +The following are considered "must-haves" of any complete API +redesign: + +- [ ] Don't require usage of Angular API. +- [ ] Don't require support for Angular API. + +# Proposals + +## RequireJS as dependency injector + +Use Require.JS for dependency injection. + +Dependencies will then be responsible for being sufficiently +mutable/extensible/customizable. This can be facilitated by +adding platform classes which can facilitate the addition +of reusable components. + +Things that we usefully acquire via dependency injection currently: + +* Services. +* Extensions (by category). +* Configuration constants. + +Services would be defined (by whatever component is responsible +for declaring it) using `define` and the explicit name of the +service. To allow for the power of composite services, the +platform would provide a `CompositeService` class that supports +this process by providing `register`, `decorate`, and `composite` +methods to register providers, decorators, and aggregators +respectively. (Note that nomenclature changes are also implied +here, to map more clearly to the Composite Pattern and to +avoid the use of the word "provider", which has ambiguity with +Angular.) + +```js +define( + "typeService", + ["CompositeService"], + function (CompositeService) { + var typeService = new CompositeService([ + "listTypes", + "getType" + ]); + + // typeService has `listTypes` and `getType` as methods; + // at this point they are stubbed (will return undefined + // or throw or similar) but this will change as + // decorators/compositors/providers are added. + + // You could build in a compositor here, or + // someone could also define one later + typeService.composite(function (typeServices) { + // ... return a TypeService + }); + + // Similarly, you could register a default implementation + // here, or from some other script. + typeService.register(function (typeService) { + // ... return a TypeService + }, { priority: 'default' }); + + return typeService; + } +); +``` + +Other code could then register additional `TypeService` +implementations (or decorators, or even compositors) by +requiring `typeService` and calling those methods; or, it +could use `typeService` directly. Priority ordering could +be utilized by adding a second "options" argument. + +For extension categories, you could simply use registries: + +```js +define( + "typeRegistry", + ["ExtensionRegistry"], + function (ExtensionRegistry) { + return new ExtensionRegistry(); + } +); +``` + +Where `ExtensionRegistry` extends `Array`, and adds a +`register` method which inserts into the array at some +appropriate point (e.g. with an options parameter that +respects priority order.) + +This makes unit testing somewhat more difficult when you +want to mock injected dependencies; there are tools out +there (e.g. [Squire](https://github.com/iammerrick/Squire.js/)) +which can help with this, however. + +### Benefits + +* Clarifies "how objects are passed around at run-time"; + answer is always "via RequireJS." +* Preserves flexibility/power provided by composite services. +* Lends itself fairly naturally to API documentation via JSDoc + (as compared to declaring things in bundles, which does not.) +* Reduces interface complexity for acquiring dependencies; + one interface for both explicit and "implicit" dependencies, + instead of separate approaches for static and substitutable + dependencies. +* Removes need to understand Angular's DI mechanism. +* Improves useability of documentation (`typeService` is an + instance of `CompositeService` and implements `TypeService` + so you can easily traverse links in the JSDoc.) +* Can be used more easily from Web Workers, allowing services + to be used on background threads trivially. + +### Detriments + +* Having services which both implement the service, and + have methods for registering the service, is a little + weird; would be cleaner if these were separate. + (Mixes concerns.) +* Syntax becomes non-declarative, which may make it harder to + understand "what uses what." +* Allows for ordering problems (e.g. you start using a + service before everything has been registered.) + +## Arbitrary HTML Views + +Currently, writing new views requires writing Angular templates. +This must change if we want to reduce our dependence on Angular. + +Instead, propose that: + +* What are currently called "views" we call something different. + (Want the term view to be more like "view" in the MVC sense.) + * For example, call them "applications." +* Consolidate what are currently called "representations" and + "templates", and instead have them be "views". + +For parity with actions, a `View` would be a constructor which +takes an `ActionContext` as a parameter (with similarly-defined +properties) and exposes a method to retrieve the HTML elements +associateed with it. + +The platform would then additionally expose an `AngularView` +implementation to improve compatibility with existing +representations, whose usage would something like: + +```js +define( + ["AngularView"], + function (AngularView) { + var template = "Hello world"; + return new AngularView(template); + } +); +``` + +The interface exposed by a view is TBD, but should provide at +least the following: + +* A way to get the HTML elements that are exposed by & managed + by the view. +* A `destroy` method to detach any listeners at the model level. + +Individual views are responsible for managing their resources, +e.g. listening to domain objects for mutation. To keep DRY, the +platform should include one or more view implementations that +can be used/subclassed which handle common behavior(s). + +### Benefits + +* Using Angular API for views is no longer required. +* Views become less-coupled to domain objects. Domain objects + may be present in the `ViewContext`, but this also might + just be a "view" of some totally different thing. +* Helps clarify high-level concerns in the API (a View is now + really more like a View in the MVC sense; although, not + completely, so this gets double-booked as a detriment.) +* Having a `ViewContext` that gets passed in allows views to + be more "contextually aware," which is something that has + been flagged previously as a UX desire. + +### Detriments + +* Becomes less clear how views relate to domain objects. +* Adds another interface. +* Leaves an open problem of how to distinguish views that + a user can choose (Plot, Scrolling List) from views that + are used more internally by the application (tree view.) +* Views are still not Views in the MVC sense (in practice, + the will likely be view-controller pairs.) We could call + them widgets to disambiguate this. +* Related to the above, even if we called these "widgets" + it would still fail to enforce good MVC. + +## Wrap Angular Services + +Wrap Angular's services in a custom interfaces; e.g. +replace `$http` with an `httpService` which exposes a useful +subset of `$http`'s functionality. + +### Benefits + +* Removes a ubiquitous dependency on Angular. +* Allows documentation for these features to be co-located + and consistent with other documentation. +* Facilitates replacing these with non-Angular versions + in the future. + +### Detriments + +* Increases the number of interfaces in Open MCT Web. (Arguably, + not really, since the same interfaces would exist if exposed + by Angular.) + +## Bundle Declarations in JavaScript + +Replace `bundle.json` files (and bundle syntax generally) with +an imperative form. There would instead be a `Bundle` interface +which scripts can implement (perhaps assisted by a platform +class.) + +The `bundles.json` file would then be replaced with a `bundles.js` +or `Bundles.js` that would look something like: + +```js +define( + [ + 'platform/core/PlatformBundle', + // ... etc ... + 'platform/features/plot/PlotBundle' + ], + function () { + return arguments; + } +); +``` + +Which could in turn be used by an initializer: + +```js +define( + ['./bundles', 'mct'], + function (bundles, mct) { + mct.initialize(bundles); + } +); +``` + +A `Bundle` would have a constructor that took some JSON object +(a `BundleContext`, lets say) and would provide methods for +application life-cycle events. Depending on other choices, +a dependency injector could be passed in at some appropriate +life-cycle call (e.g. initialize.) + +This would also allow for "composite bundles" which serve as +proxies for multiple bundles. The `BundleContext` could contain +(or later be amended to contain) filtering rules to ignore +other bundles and so forth (this has been useful for administering +Open MCT Web in subtly different configurations in the past.) + +### Benefits + +* Imperative; more explicit, less magic, more clear what is going on. +* Having a hierarchy of "bundles" could make it easier to navigate + (relevant groupings can be nested in a manner which is not + currently well-supported.) +* Lends itself naturally to a compilation step. +* Nudges plugin authors to "do your initialization and registration + in a specific place" instead of mixing in registration of features + with their implementations. + +### Detriments + +* Introduces another interface. +* Loses some of the convenience of having a declarative + summary of components and their dependencies. + +## Pass around a dependency injector + +:warning: Note that this is incompatible with the +[RequireJS as dependency injector](#requirejs-as-dependency-injector) +proposal. + +Via some means (such as in a registration lifecycle event as +described above) pass a dependency injector to plugins to allow +for dependencies to be registered. + +For example: + +```js +MyBundle.prototype.registration = function (architecture) { + architecture.service('typeService').register(MyTypeService); + architecture.extension('actions').register( + [ 'foo' ], + function (foo) { return new MyAction(foo); } + ); +}; +``` + +### Benefits + +* Ensures that registration occurs at an appropriate stage of + application execution, avoiding start-up problems. +* Makes registration explicit (generally easier to understand) + rather than implicit. +* Encapsulates dependency injection nicely. + +### Detriments + +* Increases number of interfaces to learn. +* Syntax likely to be awkward, since in many cases we really + want to be registering constructors. + +## Remove partial constructors + +Remove partial constructors; these are confusing. It is hard to +recognize which constructor arguments are from dependencies, and +which will be provided at run-time. Instead, it is the responsibility +of whoever is introducing a component to manage these things +separately. + +### Benefits + +* More clarity. + +### Detriments + +* Possibly results in redundant effort to manage this difference + (other APIs may need to be adjusted accordingly.) + +## Rename Views to Applications + +Rename (internally to the application, not necessarily in UI or +in user guide) what are currently called `views` to `applications`. + +### Benefits + +* Easier to understand. What is currently called a "view" is, + in the MVC sense, a view-controller pair, usually with its own + internal model for view state. Calling these "applications" + would avoid this ambiguity/inconsistency. +* Also provides an appropriate mindset for building these; + particularly, sets the expectation that you'll want to decompose + this "application" into smaller pieces. This nudges developers + in appropriate directions (in contrast to `views`, which + typically get implemented as templates with over-complicated + "controllers".) + +### Detriments + +* Developer terminology falls out of sync with what is used in + the user guide. + +## Provide Classes for Extensions + +As a general pattern, when introducing extension categories, provide +classes with a standard implementation of these interfaces that +plugin developers can `new` and register. + +For example, instead of declaring a type as: + +```json +{ + "types": [{ + "key": "sometype", + "glyph": "X", + "etc": "..." + }] +} +``` + +You would register one as: + +```js +// Assume we have gotten a reference to a type registry somehow +typeRegistry.register(new Type({ + "key": "sometype", + "glyph": "X", + "etc": "..." +})); +``` + +### Benefits + +* Easier to understand (less "magic"). +* Lends itself naturally to substitution of different implementations + of the same interface. +* Allows for run-time decisions about exactly what gets registered. + +### Detriments + +* Adds some modest boilerplate. +* Provides more opportunity to "do it wrong." + +## Normalize naming conventions + +Adopt and obey the following naming conventions for AMD modules +(and for injectable dependencies, which may end up being modules): + +* Use `UpperCamelCase` for classes. +* Use `lowerCase` names for instances. + * Use `someNoun` for object instances which implement some + interface. The noun should match the implemented interface, + when applicable. + * `useSomeVerb` for functions. +* Use `ALL_CAPS_WITH_UNDERSCORES` for other values, including + "struct-like" objects (that is, where the object conceptually + contains properties rather than methods.) + +### Benefits + +* Once familiar with the conventions, easier to understand what + individual modules are. + +### Detriments + +* A little bit inflexible. + +## Expose no third-party APIs + +As a general practice, expose no third-party APIs as part of the +platform. + +For cases where you do want to access third-party APIs directly +from other scripts, this behavior should be "opt-in" instead of +mandatory. For instance, to allow addition of Angular templates, +an Angular-support bundle could be included which provides an +`AngularView` class, a `controllerRegistry`, et cetera. Importantly, +such a bundle would need to be kept separate from the platform API, +or appropriately marked as non-platform in the API docs (an +`@experimental` tag would be nice here if we feel like extending +JSDoc.) + +### Benefits + +* Simplifies learning curve (only one API to learn.) +* Reduces Angular dependency. +* Avoids the problems of ubiquitous dependencies generally. + +### Detriments + +* Increases documentation burden. + +## Register Extensions as Instances instead of Constructors + +Register extensions as object instances instead of constructors. +This allows for API flexibility w.r.t. constructor signatures +(and avoids the need for partial constructors) and additionally +makes it easier to provide platform implementations of extensions +that can be used, subclassed, etc. + +For instance, instead of taking an `ActionContext` in its +constructor, an `Action` would be instantiated once and would +accept appropriate arguments to its methods: + +```js +function SomeAction { +} +SomeAction.prototype.canHandle = function (actionContext) { + // Check if we can handle this context +}; +SomeAction.prototype.perform = function (actionContext) { + // Perform this action, in this context +}; +``` + +### Benefits + +* Reduces scope of interfaces to understand (don't need to know + what constructor signature to provide for compatibility.) + +### Detriments + +* Requires refactoring of various types; may result in some + awkward APIs or extra factory interfaces. + +## Remove capability delegation + +The `delegation` capability has only been useful for the +`telemetry` capability, but using both together creates +some complexity to manage. In practice, these means that +telemetry views need to go through `telemetryHandler` to +get their telemetry, which in turn has an awkward API. + +This could be resolved by: + +* Removing `delegation` as a capability altogether. +* Reworking `telemetry` capability API to account for + the possibility of multiple telemetry-providing + domain objects. (Perhaps just stick `domainObject` + in as a field in each property of `TelemetryMetadata`?) +* Move the behavior currently found in `telemetryHandler` + into the `telemetry` capability itself (either the + generic version, or a version specific to telemetry + panels - probably want some distinct functionality + for each.) + +### Benefits + +* Reduces number of interfaces. +* Accounting for the possibility of multiple telemetry objects + in the `telemetry` capability API means that views using + this will be more immediately aware of this as a possibility. + +### Detriments + +* Increases complexity of `telemetry` capability's interface + (although this could probably be minimized.) + +## Nomenclature Change + +Instead of presenting Open MCT Web as a "framework" or +"platform", present it as an "extensible application." + +This is mostly a change for the developer guide. A +"framework" and a "platform" layer would still be useful +architecturally, but the plugin developer's mental model +for this would then be inclined toward looking at defined +extension points. The underlying extension mechanism could +still be exposed to retain the overall expressive power of +the application. + +This may subtly influence other design decisions in order +to match the "extensible application" identity. On a certain +level, this contradicts the proposal to +[rename views to applications](#rename-views-to-applications). + +### Benefits + +* May avoid incurring some of the "framework aversion" that + is common among JavaScript developers. +* More accurately describes the application. + +### Detriments + +* May also be a deterrent to developers who prefer the more + "green field" feel of developing applications on a useful + platform. + +## Capabilities as Mixins + +Change the behavior of capabilities such that they act as +mixins, adding additional methods to domain objects. +Checking if a domain object has a `persistence` capability +would instead be reduced to checking if it has a `persist` +method. + +Mixins would be applied in priority order and filtered for +applicability by policy. + +### Benefits + +* Replaces "capabilities" (which, as a concept, can be hard + to grasp) with a more familiar "mixins" concept, which has + been used more generally across many languages. +* Reduces interface depth. + +### Detriments + +* Requires checking for the interface exposed by a domain + object. Alternately, could use `instanceof`, but would + need to take care to ensure that the prototype chain of + the domain object is sufficient to do this (which may + enforce awkward or non-obvious constraints on the way these + mixins are implemented.) +* May complicate documentation; understanding the interface + of a given domain object requires visiting documentation + for various mixins. + +## Remove Applies-To Methods + +Remove all `appliesTo` static methods and replace them with +appropriate policy categories. + +### Benefits + +* Reduces sizes of interfaces. Handles filtering down sets + of extensions in a single consistent way. + +### Detriments + +* Mixes formal applicability with policy; presently, `appliesTo` + is useful for cases where a given extension cannot, even in + principle, be applied in a given context (e.g. a domain object + model is missing the properties which describe the behavior), + whereas policy is useful for cases where applicability is + being refined for business or usability reasons. Colocating + the former with the extension itself has some benefits + (exhibits better cohesion.) + * This could be mitigated in the proposed approach by locating + `appliesTo`-like policies in the same bundle as the relevant + extension. + +## Revise Telemetry API + +Revise telemetry API such that: + +* `TelemetrySeries` is replaced with arrays of JavaScript objects + with properties. +* It is no longer necessary to use `telemetryHandler` (plays well + with proposal to + [remove capability delegation](#remove-capability delegation)) +* Change `request` call to take a callback, instead of returning + a promise. This allows that callback to be invoked several + times (e.g. for progressive loading, or to reflect changes from + the time conductor.) + +Should also consider: + +* Merge `subscribe` functionality into `request`; that is, handle + real-time data as just another thing that triggers the `request` + callback. +* Add a useful API to telemetry metadata, allowing things like + formats to be retrieved directly from there. + +As a consequence of this, `request` would need to return an object +representing the active request. This would need to be able to +answer the following questions and provide the following behavior: + +* Has the request been fully filled? (For cases like progressive + loading?) +* What data has changed since the previous callback? (To support + performance optimizations in plotting; e.g. append real-time + data.) +* Stop receiving updates for this request. +* Potentially, provide utility methods for dealing with incoming + data from the request. + +Corollary to this, some revision of `TelemetryMetadata` properties +may be necessary to fully and usably describe the contents of +a telemetry series. + +### Benefits + +* Reduces interface depth. +* Reduces interface size (number of methods.) +* Supports a broader range of behaviors (e.g. progressive loading) + within the same interface. + +### Detriments + +* Merging with `subscribe` may lose the clarity/simplicity of the + current API. + +## Allow Composite Services to Fail Gracefully + +Currently, when no providers are available for a composite service +that is depended-upon, dependencies cannot be resolved and the +application fails to initialize, with errors appearing in the +developer console. + +This is acceptable behavior for truly unrecoverable missing +dependencies, but in many cases it would be preferable to allow a +given type of composite service to define some failure behavior +when no service of an appropriate type is available. + +To address this: + +* Provide an interface (preferably + [imperative](#bundle-Declarations-in-javascript)) + for declaring composite services, independent of any implementation + of an aggregator/decorator/provider. This allows the framework + layer to distinguish between unimplemented dependencies (which + could have defined failover strategies) from undefined dependencies + (which cannot.) +* Provide a default strategy for service composition that picks + the highest-priority provider, and logs an error (and fails to + satisfy) if no providers have been defined. +* Allow this aggregation strategy to be overridden, much as one + can declare aggregators currently. However, these aggregators should + get empty arrays when no providers have been registered (instead of + being ignored), at which point they can decide how to handle this + situation (graceful failure when it's possible, noisy errors when + it is not.) + +### Benefits + +* Allows for improved robustness and fault tolerance. +* Makes service declarations explicit, reducing "magic." + +### Detriments + +* Requires the inclusion of software units which define services, + instead of inferring their existence (slight increase in amount + of code that needs to be written.) +* May result in harder-to-understand errors when overridden + composition strategies do not failover well (that is, when they + do need at least implementation, but fail to check for this.) + +## Plugins as Angular Modules + +Do away with the notion of bundles entirely; use Angular modules +instead. Registering extensions or components of composite services +would then be handled by configuring a provider; reusable classes +could be exposed by the platform for these. + +Example (details are flexible, included for illustrative purposes): + +```javascript +var mctEdit = angular.module('mct-edit', ['ng', 'mct']); + +// Expose a new extension category +mctEdit.provider('actionRegistry', ExtensionCategoryProvider); + +// Expose a new extension +mctEdit.config(['actionRegistryProvider', function (arp) { + arp.register(EditPropertiesAction); +}]) + +return mctEdit; +``` + +Incompatible with proposal to +(expose no third-party APIs)[#expose-no-third-party-apis]; Angular +API would be ubiquitously exposed. + +This is a more specific variant of +(Bundle Declarations in JavaScript)[#bundle-declarations-in-javascript]. + +### Benefits + +* Removes a whole category of API (bundle definitions), reducing + learning curve associated with the software. +* Closer to Angular style, reducing disconnect between learning + Angular and learning Open MCT Web (reducing burden of having + to learn multiple paradigms.) +* Clarifies "what can be found where" (albeit not perfectly) + since you can look to module dependencies and follow back from there. + +### Detriments + +* Hardens dependency on Angular. +* Increases depth of understanding required of Angular. +* Increases amount of boilerplate (since a lot of this has + been short-handed by existing framework layer.) + +## Contextual Injection + +For extensions that depend on certain instance-level run-time +properties (e.g. actions or views which use objects and/or specific +capabilities of those objects), declare these features as dependencies +and expose them via dependency injection. (AngularJS does this for +`$scope` in the context of controllers, for example.) + +A sketch of an implementation for this might look like: + +```js +function ExtensionRegistry($injector, extensions, getLocals) { + this.$injector = $injector; + this.extensions = extensions; + this.getLocals = getLocals; +} +ExtensionRegistry.prototype.get = function () { + var $injector = this.$injector, + locals = this.getLocals.apply(null, arguments); + return this.extensions.filter(function (extension) { + return depsSatisfiable(extension, $injector, locals); + }).map(function (extension) { + return $injector.instantiate(extension, locals); + }); +}; + + +function ExtensionRegistryProvider(getLocals) { + this.getLocals = getLocals || function () { return {}; }; + this.extensions = []; +} +ExtensionRegistryProvider.prototype.register = function (extension) { + this.extensions.push(extension); +}; +ExtensionRegistryProvider.prototype.$get = ['$injector', function ($injector) { + return new ExtensionRegistry($injector, this.extensions, this.getLocals); +}]; +``` + +Extension registries which need to behave context-sensitively could +subclass this to describe how these contextual dependencies are satisfied +(for instance, by returning various capability properties in `getLocals`). + +Specific extensions could then declare dependencies as appropriate to the +registry they are using: + +```js +app.config(['actionRegistryProvider', function (arp) { + arp.register(['contextCapability', 'domainObject', RemoveAction]); +}]); +``` + +### Benefits + +* Allows contextual dependencies to be fulfilled in the same (or similar) + manner as global dependencies, increasing overall consistency of API. +* Clarifies dependencies of individual extensions (currently, extensions + themselves or policies generally need to imperatively describe what + dependencies will be used in order to filter down to applicable + extensions.) +* Factors out some redundant code from relevant extensions; actions, + for instance, no longer need to interpret an `ActionContext` object. + Instead, their constructors take inputs that are more relevant to + their behavior. +* Removes need for partial construction, as any arguments which would + normally be appended after partialization can instead be declared as + dependencies. Constructors in general become much less bound to the + specifics of the platform. + +### Detriments + +* Slightly increases dependency on Angular; other dependency injectors + may not offer comparable ways to specificy dependencies non-globally. +* Not clear (or will take effort to make clear) which dependencies are + available for which extensions. Could be mitigated by standardizing + descriptions of context across actions and views, but that may offer + its own difficulties. +* May seem counter-intuitive coming from "vanilla" AngularJS, where + `$scope` is the only commonly-used context-sensitive dependency. + +## Add new abstractions for actions + +Originally suggested in +[this comment](https://github.com/nasa/openmctweb/pull/69#issuecomment-156199991): + +> I think there are some grey areas with actions: are they all directly +tied to user input? If so, why do they have any meaning in the back end? +Maybe we should look at different abstractions for actions: + +> * `actions` - the basic implementation of an action, essentially a + function declaration. for example, `copy` requires arguments of + `object` and a `target` to place the object in. at this level, + it is reusable in a CLI. +> * `context menu actions` - has criteria for what it applies to. + when it is visible, and defines how to get extra > input from a + user to complete that action. UI concern only. +> * `gesture-handler` - allows for mapping a `gesture` to an action, + e.g. drag and drop for link. UI Concern only. + +> We could add context menu actions for domain objects which navigate +to that object, without having to implement an action that has no real +usage on a command line / backend. + +### Benefits + +* Clearly delineates concerns (UI versus model) + +### Detriments + +* Increases number of interfaces. + +## Add gesture handlers + +See [Add new abstractions for actions](#add-new-abstractions-for-actions); +adding an intermediary between gestures and the actions that they +trigger could be useful in separating concerns, and for more easily +changing mappings in a mobile context. + +### Benefits + +* Clearly decouples UI concerns from the underlying model changes + they initiate. +* Simplifies and clarifies mobile support. + +### Detriments + +* Increases number of interfaces. + +# Decisions + +After review on Dec. 8, 2015, team consensus on these proposals is +as follows: + +Proposal | @VWoeltjen | @larkin | @akhenry | Consensus +----|:---:|:---:|:---:|:---: +RequireJS as dependency injector | :-1: | :neutral_face: :question: | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44349731) | [:question:](https://github.com/nasa/openmctweb/issues/461) +Arbitrary HTML Views | :+1: | :+1: | | [:+1: 1](https://github.com/nasa/openmctweb/issues/463) +Wrap Angular Services | :-1: | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44355057) | :no_entry_sign: +Bundle Declarations in JavaScript | :+1: | :neutral_face: :question: | | [:+1:](https://github.com/nasa/openmctweb/issues/450) +Pass around a dependency injector | :-1: | :-1: | | :-1: +Remove partial constructors | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) +Rename Views to ~~Applications~~ | :+1: | :neutral_face: :question: | | [:+1: 2](https://github.com/nasa/openmctweb/issues/463) +Provide Classes for Extensions | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) +Normalize naming conventions | :+1: | :+1: | | :+1: +Expose no third-party APIs | :+1: * | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) | [:+1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) † | :+1: 3 +Register Extensions as Instances instead of Constructors | :+1: | :-1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) +Remove capability delegation | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/463) +Nomenclature Change | :+1: | [:+1:](https://github.com/nasa/openmctweb/issues/229#issuecomment-153453035) | | :white_check_mark: ‡ +Capabilities as Mixins | | :+1: | [:+1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44355473) | [:question: 4](https://github.com/nasa/openmctweb/issues/463) +Remove Applies-To Methods | | :-1: | | :-1: +Revise Telemetry API | :+1: | :+1: | | [:+1: 5](https://github.com/nasa/openmctweb/issues/463) +Allow Composite Services to Fail Gracefully | :+1: | :-1: | | [:+1: 6](https://github.com/nasa/openmctweb/issues/463) +Plugins as Angular Modules | :+1: | :neutral_face: :question: | | [:question:](https://github.com/nasa/openmctweb/issues/461) +Contextual Injection | | :-1: | | [:question:](https://github.com/nasa/openmctweb/issues/461) +Add new abstractions for actions | [:-1:](https://github.com/nasa/openmctweb/pull/69#issuecomment-158172485) :question: | :+1: | | :-1: +Add gesture handlers | :+1: | :+1: :question: | | [:+1:](https://github.com/nasa/openmctweb/issues/463) + +* Excepting Angular APIs. Internally, continue to use code style +where classes are declared separately from their registration, such +that ubiquity of Angular dependency is minimized. + +† "I think we should limit the third party APIs we expose to +one or two, but I worry it might be counterproductive to +completely hide them." + +‡ Some ambiguity about what to call ourselves if not a platform, +but general agreement that "platform" is not a good term. +More Detail on Pete's Opinions Here: +https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign_PeteRichards.md#notes-on-current-api-proposals + +1 Needs to be designed carefully; don't want to do this with +a complicated interface, needs to be significantly simpler than wrapping +with an Angular directive would be. + +2 Agree that we need a new name, but it should not be "application" + +3 Don't want to expose (or require usage of) third-party +APIs generally. Angular may be an exception in the sense that it is an +API we presume to be present. Can use third-party APIs internally, but +don't want to support them or be tied to them. + +4 Want to have a separate spin-off discussion about +capabilities. Want to consider several alternatives here. +At minimum, though, mixins would be an improvement relative +to how these are currently handled. + +5 Agree we want to revise APIs, but this should +be a larger spin-off. + +6 Not necessarily as described, but expected to be a +property of composite services in whatever formulation they +take. Should not be default behavior. + + +[Additional proposals](APIRedesign_PeteRichards.md) considered: + +Proposal | Consensus +------|------ +Imperitive component registries | [:+1:](https://github.com/nasa/openmctweb/issues/462) +Get rid of "extension category" concept. | [:+1:](https://github.com/nasa/openmctweb/issues/462) +Reduce number and depth of extension points | :+1: +Composite services should not be the default | [:question:](https://github.com/nasa/openmctweb/issues/463) +Get rid of views, representations, and templates. | [:+1: 1](https://github.com/nasa/openmctweb/issues/463) +More angular: for all services | [:question:](https://github.com/nasa/openmctweb/issues/461) +Less angular: only for views | [:question:](https://github.com/nasa/openmctweb/issues/461) +Use systemjs for module loading | [:+1: 2](https://github.com/nasa/openmctweb/issues/459) +Use gulp or grunt for standard tooling | [:+1:](https://github.com/nasa/openmctweb/issues/459) +Package openmctweb as single versioned file. | [:+1:](https://github.com/nasa/openmctweb/issues/458) +Refresh on navigation | [:+1: 3](https://github.com/nasa/openmctweb/issues/463) +Move persistence adapter to promise rejection. | [:+1:](https://github.com/nasa/openmctweb/issues/463) +Remove bulk requests from providers | [:+1: 4](https://github.com/nasa/openmctweb/issues/463) + +1 Need to agree upon details at design-time, but +basic premise is agreed-upon - want to replace +views/representations/templates with a common abstraction +(and hoist out the non-commonalities to other places as appropriate) + +2 Beneficial but not strictly necessary (may be +lower-effort alternatives); should prioritize accordingly during planning + +3 Some effort will be required to make all of the state +that needs to persist among route changes actually be persistent. +Will want to address this at design-time (will want to look at +libraries to simplify this, for instance) + +4 Maybe not all providers, but anywhere there is not a +strong case for building batching into the API we should prefer +simplicity. (Want to pay specific attention to telemetry here.) diff --git a/docs/src/design/proposals/APIRedesign_PeteRichards.md b/docs/src/design/proposals/APIRedesign_PeteRichards.md new file mode 100644 index 0000000000..3fa78fe2ed --- /dev/null +++ b/docs/src/design/proposals/APIRedesign_PeteRichards.md @@ -0,0 +1,251 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Reducing interface depth (the bundle.json version)](#reducing-interface-depth-the-bundlejson-version) + - [Imperitive component registries](#imperitive-component-registries) + - [Get rid of "extension category" concept.](#get-rid-of-extension-category-concept) + - [Reduce number and depth of extension points](#reduce-number-and-depth-of-extension-points) + - [Composite services should not be the default](#composite-services-should-not-be-the-default) + - [Get rid of views, representations, and templates.](#get-rid-of-views-representations-and-templates) +- [Reducing interface depth (The angular discussion)](#reducing-interface-depth-the-angular-discussion) + - [More angular: for all services](#more-angular-for-all-services) + - [Less angular: only for views](#less-angular-only-for-views) +- [Standard packaging and build system](#standard-packaging-and-build-system) + - [Use systemjs for module loading](#use-systemjs-for-module-loading) + - [Use gulp or grunt for standard tooling](#use-gulp-or-grunt-for-standard-tooling) + - [Package openmctweb as single versioned file.](#package-openmctweb-as-single-versioned-file) +- [Misc Improvements](#misc-improvements) + - [Refresh on navigation](#refresh-on-navigation) + - [Move persistence adapter to promise rejection.](#move-persistence-adapter-to-promise-rejection) + - [Remove bulk requests from providers](#remove-bulk-requests-from-providers) +- [Notes on current API proposals:](#notes-on-current-api-proposals) +- [[1] Footnote: The angular debacle](#1-footnote-the-angular-debacle) + - ["Do or do not, there is no try"](#do-or-do-not-there-is-no-try) + - [A lack of commitment](#a-lack-of-commitment) + - [Commitment is good!](#commitment-is-good) + + + + +# Reducing interface depth (the bundle.json version) + +## Imperitive component registries + +Transition component registries to javascript, get rid of bundle.json and bundles.json. Prescribe a method for application configuration, but allow flexibility in how application configuration is defined. + +Register components in an imperitive fashion, see angularApp.factory, angularApp.controller, etc. Alternatively, implement our own application object with new registries and it's own form of registering objects. + +## Get rid of "extension category" concept. + +The concept of an "extension category" is itself an extraneous concept-- an extra layer of interface depth, an extra thing to learn before you can say "hello world". Extension points should be clearly supported and documented with whatever interfaces make sense. Developers who wish to add something that is conceptually equivalent to an extension category can do so directly, in the manner that suites their needs, without us forcing a common method on them. + +## Reduce number and depth of extension points + +Clearly specify supported extension points (e.g. persistence, model providers, telemetry providers, routes, time systems), but don't claim that the system has a clear and perfect repeatable solution for unknown extension types. New extension categories can be implemented in whatever way makes sense, without prescribing "the one and only system for managing extensions". + +The underlying problem here is we are predicting needs for extension points where none exist-- if we try and design the extension system before we know how it is used, we design the wrong thing and have to rewrite it later. + +## Composite services should not be the default + +Understanding composite services, and describing services as composite services can confuse developers. Aggregators are implemented once and forgotten, while decorators tend to be hacky, brittle solutions that are generally needed to avoid circular imports. While composite services are a useful construct, it reduces interface depth to implement them as registries + typed providers. + +You can write a provider (provides "thing x" for "inputs y") with a simple interface. A provider has two or more methods: +* a method which takes "inputs y" and returns True if it knows how to provide "thing x", false otherwise. +* one or more methods which provide "thing x" for objects of "inputs y". + +Actually checking whether a provider can respond to a request before asking it to do work allows for faster failure and clearer errors when no providers match the request. + +## Get rid of views, representations, and templates. + +Templates are an implementation detail that should be handled by module loaders. Views and representations become "components," and a new concept, "routes", is used to exposing specific views to end users. + +`components` - building blocks for views, have clear inputs and outputs, and can be coupled to other components when it makes sense. (e.g. parent-child components such as menu and menu item), but should have ZERO knowledge of our data models or telemetry apis. They should define data models that enable them to do their job well while still being easy to test. + +`routes` - a view type for a given domain object, e.g. a plot, table, display layout, etc. Can be described as "whatever shows in the main screen when you are viewing an object." Handle loading of data from a domain object and passing that data to the view components. Routes should support editing as it makes sense in their own context. + +To facilitate testing: + +* routes should be testable without having to test the actual view. +* components should be independently testable with zero knowledge of our data models or telemetry APIs. + +Component code should be organized side by side, such as: + +``` +app +|- components + |- productDetail + | |- productDetail.js + | |- productDetail.css + | |- productDetail.html + | |- productDetailSpec.js + |- productList + |- checkout + |- wishlist +``` + +Components are not always reusable, and we shouldn't be overly concerned with making them so. If components are heavily reused, they should either be moved to a platform feature (e.g. notifications, indicators), or broken off as an external dependency (e.g. publish mct-plot as mct-plot.js). + + +# Reducing interface depth (The angular discussion) + +Two options here: use more angular, use less angular. Wrapping angular methods does not reduce interface depth and must be avoided. + +The primary issue with angular is duplications of concerns-- both angular and the openmctweb platform implement the same tools side by side and it can be hard to comprehend-- it increases interface depth. For other concerns, see footnotes[1]. + +Wrapping angular methods for non-view related code is confusing to developers because of the random constraints angular places on these items-- developers ultimately have to understand both angular DI and our framework. For example, it's not possible to name the topic service "topicService" because angular expects Services to be implemented by Providers, which is different than our expectation. + +To reduce interface depth, we can replace our own provider and registry patterns with angular patterns, or we can only utilize angular view logic, and only use our own DI patterns. + +## More angular: for all services + +Increasing our commitment to angular would mean using more of the angular factorys, services, etc, and less of our home grown tools. We'd implement our services and extension points as angular providers, and make them configurable via app.config. + +As an example, registering a specific type of model provider in angular would look like: + +```javascript +mct.provider('model', modelProvider() { /* implementation */}); + +mct.config(['modelProvider', function (modelProvider) { + modelProvider.providers.push(RootModelProvider); +}]); +``` + +## Less angular: only for views + +If we wish to use less angular, I would recommend discontinuing use of all angular components that are not view related-- services, factories, $http, etc, and implementing them in our own paradigm. Otherwise, we end up with layered interfaces-- one of the goals we would like to avoid. + + +# Standard packaging and build system + +Standardize the packaging and build system, and completely separate the core platform from deployments. Prescribe a starting point for deployments, but allow flexibility. + +## Use systemjs for module loading + +Allow developers to use whatever module loading system they'd like to use, while still supporting all standard cases. We should also use this system for loading assets (css, scss, html templates), which makes it easier to implement a single file deployment using standard build tooling. + +## Use gulp or grunt for standard tooling + +Using gulp or grunt as a task runner would bring us in line with standard web developer workflows and help standardize rendering, deployment, and packaging. Additional tools can be added to the workflow at low cost, simplifying the set up of developer environments. + +Gulp and grunt provide useful developer tooling such as live reload, automatic scss/less/etc compiliation, and ease of extensibility for standard production build processes. They're key in decoupling code. + +## Package openmctweb as single versioned file. + +Deployments should depend on a specific version of openmctweb, but otherwise be allowed to have their own deployment and development toolsets. + +Customizations and deployments of openmctweb should not use the same build tooling as the core platform; instead they should be free to use their own build tools as they wish. (We would provide a template for an application, based on our experience with warp-for-rp and vista) + +Installation and utilization of openmctweb should be as simple as downloading the js file, including it in your own html page, and then initializing an app and running it. If a developer would prefer, they could use bower or npm to handle installation. + +Then, if we're using imperative methods for extending the application we can use the following for basic customization: + +```html + + +``` + +This packaging reduces the complexity of managing multiple deployed versions, and also allows us to provide users with incredibly simple tutorials-- they can use whatever tooling they like. For instance, a hello world tutorial may take the option of "exposing a new object in the tree". + +```javascript +var myApp = new OpenMCTWeb(); +myApp.roots.addRoot({ + id: 'myRoot', + name: 'Hello World!', +}); +myApp.routes.setDefault('myRoot'); +myApp.run(); +``` + +# Misc Improvements + +## Refresh on navigation +In cases where navigation events change the entire screen, we should be using routes and location changes to navigate between objects. We should be using href for all navigation events. + +At the same time, navigating should refresh state of every visible object. A properly configured persistence store will handle caching with standard cache headers and 304 not modified responses, which will provide good performance of object reloads, while helping us ensure that objects are always in sync between clients. + +View state (say, the expanded tree nodes) should not be tied to caching of data-- it should be something we intentionally persist and restore with each navigation. Data (such as object definitions) should be reloaded from server as necessary to restore state. + +## Move persistence adapter to promise rejection. +Simple: reject on fail, resolve on success. + +## Remove bulk requests from providers + +Aggregators can request multiple things at once, but individual providers should only have to implement handling at the level of a single request. Each provider can implement it's own internal batching, but it should support making requests at a finer level of detail. + +Excessive wrapping of code with $q.all causes additional digest cycles and decreased performance. + +For example, instead of every telemetry provider responding to a given telemetry request, aggregators should route each request to the first provider that can fulfill that request. + + +# Notes on current API proposals: + +* [RequireJS for Dependency Injection](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#requirejs-as-dependency-injector): requires other topics to be discussed first. +* [Arbitrary HTML Views](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#arbitrary-html-views): think there is a place for it, requires other topics to be discussed first. +* [Wrap Angular Services](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#wrap-angular-services): No, this is bad. +* [Bundle definitions in Javascript](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#bundle-declarations-in-javascript): Points to a solution, but ultimately requires more discussion. +* [pass around a dependency injector](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#pass-around-a-dependency-injector): No. +* [remove partial constructors](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-partial-constructors): Yes, this should be superseded by another proposal though. The entire concept was a messy solution to dependency injection issues caused by declarative syntax. +* [Rename views to applications](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#rename-views-to-applications): Points to a problem that needs to be solved but I think the name is bad. +* [Provide classes for extensions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#provide-classes-for-extensions): Yes, in specific places +* [Normalize naming conventions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#normalize-naming-conventions): Yes. +* [Expose no third-party APIs](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#expose-no-third-party-apis): Completely disagree, points to a real problem with poor angular integration. +* [Register Extensions as Instances instead of Constructors](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#register-extensions-as-instances-instead-of-constructors): Superseded by the fact that we should not hope to implement a generic construct. +* [Remove capability delegation](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-capability-delegation): Yes. +* [Nomenclature Change](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#nomenclature-change): Yes, hope to discuss the implications of this more clearly in other proposals. +* [Capabilities as mixins](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#capabilities-as-mixins): Yes. +* [Remove appliesTo methods](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-applies-to-methods): No-- I think some level of this is necessary. I think a more holistic approach to policy is needed. it's a rather complicated system. +* [Revise telemetry API](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#revise-telemetry-api): If we can rough out and agree to the specifics, then Yes. Needs discussion. +* [Allow composite services to fail gracefully](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#allow-composite-services-to-fail-gracefully): No. As mentioned above, I think composite services themselves should be eliminated for a more purpose bound tool. +* [Plugins as angular modules](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#plugins-as-angular-modules): Should we decide to embrace Angular completely, I would support this. Otherwise, no. +* [Contextual Injection](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#contextual-injection): No, don't see a need. +* [Add New Abstractions for Actions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#add-new-abstractions-for-actions): Worth a discussion. +* [Add gesture handlers](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#add-gesture-handlers): Yes if we can agree on details. We need a platform implementation that is easy to use, but we should not reinvent the wheel. + + + +# [1] Footnote: The angular debacle + +## "Do or do not, there is no try" + +A commonly voiced concern of embracing angular is the possibility of becoming dependent on a third party framework. This concern is itself detrimental-- if we're afraid of becoming dependent on a third party framework, then we will do a bad job of using the framework, and inevitably will want to stop using it. + +If we're using a framework, we need to use it fully, or not use it at all. + +## A lack of commitment + +A number of the concerns we heard from developers and interns can be attributed to the tenuous relationship between the OpenMCTWeb platform and angular. We claimed to be angular, but we weren't really angular. Instead, we are caught between our incomplete framework paradigm and the angular paradigm. In many cases we reinvented the wheel or worked around functionality that angular provides, and ended up in a more confusing state. + +## Commitment is good! + +We could just be an application that is built with angular. + +An application that is modular and extensible not because it reinvents tools for providing modularity and extensibility, but because it reuses existing tools for modularity and extensibility. + +There are benefits to buying into the angular paradigm: shift documentation burden to external project, engage a larger talent pool available both as voluntary open source contributors and as experienced developers for hire, and gain access to an ecosystem of tools that we can use to increase the speed of development. + +There are negatives too: Angular is a monolith, it has performance concerns, and an unclear future. If we can't live with it, we should look at alternatives. + diff --git a/docs/src/design/proposals/ImperativePlugins.md b/docs/src/design/proposals/ImperativePlugins.md new file mode 100644 index 0000000000..1fbb83b1bc --- /dev/null +++ b/docs/src/design/proposals/ImperativePlugins.md @@ -0,0 +1,164 @@ +# Imperative Plugins + +This is a design proposal for handling +[bundle declarations in JavaScript]( +APIRedesign.md#bundle-declarations-in-javascript). + +## Developer Use Cases + +Developers will want to use bundles/plugins to (in rough order +of occurrence): + +1. Add new extension instances. +2. Use existing services +3. Add new service implementations. +4. Decorate service implementations. +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, +which has been cited as a source of confusion. + +## Interfaces + +Two base classes may be used to satisfy these use cases: + + * The `CompositeServiceFactory` provides 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 `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 +more general-purpose `Registry` + +```nomnoml +[Factory. + | + - factoryFn : function (V) : T + | + + decorate(decoratorFn : function (T, V) : T, options? : RegistrationOptions) +]-:>[function (V) : T] + +[RegistrationOptions | + + priority : number or string +] + +[Registry. + | + - compositorFn : function (Array.) : V + | + + register(item : T, options? : RegistrationOptions) + + composite(compositorFn : function (Array.) : V, options? : RegistrationOptions) +]-:>[Factory.] +[Factory.]-:>[Factory.] + +[ExtensionRegistry.]-:>[Registry.>] +[Registry.>]-:>[Registry.] + +[CompositeServiceFactory.]-:>[Registry.] +[Registry.]-:>[Registry.] +``` + +## Examples + +### 1. Add new extension instances. + +```js +// 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 + +```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. +* Relies on factories, increasing number of interfaces to be concerned + with. \ No newline at end of file diff --git a/docs/src/design/proposals/Roles.md b/docs/src/design/proposals/Roles.md new file mode 100644 index 0000000000..6148d47566 --- /dev/null +++ b/docs/src/design/proposals/Roles.md @@ -0,0 +1,138 @@ +# Roles + +Roles are presented as an alternative formulation to capabilities +(dynamic behavior associated with individual domain objects.) + +Specific goals here: + +* Dependencies of individual scripts should be clear. +* Domain objects should be able to selectively exhibit a wide + variety of behaviors. + +## Developer Use Cases + +1. Checking for the existence of behavior. +2. Using behavior. +3. Augmenting existing behaviors. +4. Overriding existing behaviors. +5. Adding new behaviors. + +## Overview of Proposed Solution + +Remove `getCapability` from domain objects; add roles as external +services which can be applied to domain objects. + +## Interfaces + +```nomnoml +[Factory. + | + - factoryFn : function (V) : T + | + + decorate(decoratorFn : function (T, V) : T, options? : RegistrationOptions) +]-:>[function (V) : T] + +[RegistrationOptions | + + priority : number or string +]<:-[RoleOptions | + + validate : function (DomainObject) : boolean +] + +[Role. | + + validate(domainObject : DomainObject) : boolean + + decorate(decoratorFn : function (T, V) : T, options? : RoleOptions) +]-:>[Factory.] +[Factory.]-:>[Factory.] +``` + +## Examples + +### 1. Checking for the existence of behavior + +```js +function PlotViewPolicy(telemetryRole) { + this.telemetryRole = telemetryRole; +} +PlotViewPolicy.prototype.allow = function (view, domainObject) { + return this.telemetryRole.validate(domainObject); +}; +``` + +### 2. Using behavior + +```js +PropertiesAction.prototype.perform = function () { + var mutation = this.mutationRole(this.domainObject); + return this.showDialog.then(function (newModel) { + return mutation.mutate(function () { + return newModel; + }); + }); +}; +``` + +### 3. Augmenting existing behaviors + +```js +// Non-Angular style +mct.roles.persistenceRole.decorate(function (persistence) { + return new DecoratedPersistence(persistence); +}); + +// Angular style +myModule.decorate('persistenceRole', ['$delegate', function ($delegate) { + return new DecoratedPersistence(persistence); +}]); +``` + +### 4. Overriding existing behaviors + +```js +// Non-Angular style +mct.roles.persistenceRole.decorate(function (persistence, domainObject) { + return domainObject.getModel().type === 'someType' ? + new DifferentPersistence(domainObject) : + persistence; +}, { + validate: function (domainObject, next) { + return domainObject.getModel().type === 'someType' || next(); + } +}); +``` + +### 5. Adding new behaviors + +```js +function FooRole() { + mct.Role.apply(this, [function (domainObject) { + return new Foo(domainObject); + }]); +} + +FooRole.prototype = Object.create(mct.Role.prototype); + +FooRole.prototype.validate = function (domainObject) { + return domainObject.getModel().type === 'some-type'; +}; + +// +myModule.roles.fooRole = new FooRole(); +``` + + +## Evaluation + +### Benefits + +* Simplifies/standardizes augmentation or replacement of behavior associated + with specific domain objects. +* Minimizes number of abstractions; roles are just factories. +* Clarifies dependencies; roles used must be declared/acquired in the + same manner as services. + +### Detriments + +* Externalizes functionality which is conceptually associated with a + domain object. +* Relies on factories, increasing number of interfaces to be concerned + with. \ No newline at end of file