Hello,
this is a follow-up to my oVirt.js announcement email, I'd like to give
you a quick summary of API & implementation design. In other words, I'll
try to describe key decisions in oVirt.js API & implementation.
1, General design
We're trying to follow best practices in JavaScript development, such as:
- containing the whole SDK in a single global variable (because until ES6
we cannot truly avoid global variables) and using IIFE/closure for better
information hiding
- using ES5 "use strict" (Strict Mode) on function level (so that Strict
Mode compliant environments will detect problems early) while not relying
on semantical differences introduced by Strict Mode (main reason = IE9)
2, API design
As presented in earlier "oVirt JavaScript SDK: Design Proposal" session,
our main goals here are API usability and readability. (If you missed
this session before, let me know and I'll forward you the slides.)
- using fluent interface concept, for example:
ovirt.api.datacenters.list().success(dcsListedCallback).run();
- separating the concept of resource (collection) vs. operation
(operation = reusable RESTful method abstraction)
- separating three logical namespaces:
* ovirt.svc -- services used by API implementation
Each service should be an object containing reusable methods.
1. existing services can be overridden to achieve SDK portability
for given runtime environment.
2. existing services can be used and new services can be added by
SDK extensions or clients.
* ovirt.util -- utilities used by API implementation
Unlike services, utilities are meant to assist with writing more
efficient JavaScript code.
* ovirt.api -- API as the main entry point to SDK functionality
3, Implementation design
I've tried to catch up with my JavaScript knowledge recently, but I'm
not an expert, I'm still learning things. Following are ideas which I
took into practice within oVirt.js API implementation:
- creating objects via mixins, each mixin is a function which gets some
object ("that") and adds new properties to that object (augmentation)
- simple dependency injection mechanism for mixins, with dependencies
resolved against "ovirt.svc" namespace, for example:
// Create new object, "spec" = information needed for object creation.
function makeSomeObject (spec) {
return mixins.mix(spec,
[mixinOne, mixinTwo], // array of mixin functions
['depNameOne', 'depNameTwo'] // array of dependency names
);
}
// "that" = object to augment.
// "spec" = information provided for object creation.
// "my" = object containing shared secrets, for more details see [1]
function mixinOne (that, spec, my) {
// Augment "that" with "quack" method, utilize closure to capture
// "spec.name" without exposing it to users of "that" object.
that.quack = function () { console.log(spec.name + ' says quack!');
// Consume "depNameOne" service, resolved from ovirt.svc namespace
that.doSomething = function () { my.depNameOne.whatever(); }
}
[1] JavaScript: The Good Parts (2008) by Douglas Crockford,
chapter "Inheritance", section "Functional" - explanation
of "my" object within functional constructor pattern
We are NOT using "new" operator in JavaScript. We are also NOT relying
on object's prototype chain. We are simply creating useful objects via
mixins, which is in my opinion the most simple and flexible way.
Regards,
Vojtech