mirror of
https://github.com/nasa/openmct.git
synced 2025-06-01 07:00:49 +00:00
1051 lines
37 KiB
Markdown
1051 lines
37 KiB
Markdown
# 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 = "<span ng-click='...'>Hello world</span>";
|
|
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.
|
|
|
|
### 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.)
|