Objects are the api, stupid!

@hutkev

Feb 2013

API Design

I was in a hotel where they had three little bottles on the counter. One was named "Shine," and one was named "Smooth," and one was named something else. One of them was hand lotion, one was soap, and one was shampoo. It was a puzzle-solving exercise to figure out which was which. They are indulging the designer's own desire to be creative, rather than trying to figure out, Okay, how can I make this as easy for the user as possible? I don't want to use my creative energy on somebody else's user interface.
 
Jeff Bezos

On a promise...

// Promises/A
interface Promise {
then( callback: (value:any) -> void, 
errback?: (err:any) -> void) 
) : Promise resolve(value: any) : void reject(value: any) : void
}
// Underlying abstraction interface Event {
value: any; // One time set, multiple get
next: (event: Event) -> void; // Stack the callbacks }


Minimising surface area often improves comprehension
However, usually requires more internals

When it gets really messy


What's in a Proxy?

var tester = new Tester()
var obj = Proxy.create(new ForwardHandler(tester))
interface Handler /* harmony:proxies style */{ defineProperty(name: string, desc: PropertyDescriptor): void; delete (name: string): bool; getPropertyNames(name: string): string[]; getOwnPropertyNames(name: string): string[]; getOwnPropertyDescriptor(name: string): PropertyDescriptor; getPropertyDescriptor(name: string): PropertyDescriptor; }

Virtual Properties

Handler:
  addProperty(name: string, writeCB: (any) => void ) {
    var that = this;
    var desc: PropertyDescriptor = {
      get: function () {
        return that.props[name].value;
      },
      set: function(value) {
        writeCB(value);
        that.props[name].value = value;
      }
    }
    this.props[name] = { value: null, pd: desc };
  }
My Object:
  constructor () {
    this.handler = new VirtualHandler(this);
    this.handler.addProperty("foo", function (value) {
      console.log('New foo: ' + value);
    });
    return Proxy.create(this.handler,this);
  }

Proxy Support (ES6)

Node supports:
http://wiki.ecmascript.org/doku.php?id=harmony:proxies

Newer version
http://wiki.ecmascript.org/doku.php?id=direct_proxies

Compatibility Shim
https://github.com/tvcutsem/harmony-reflect
Firefox 4 - Chrome 19 -  node v0.7.8

Need to work hard to be transparent & composable
Synchronous by default makes this rather difficult

Object.observe (ES7)

Observe object without changing it
Chrome Canary has test implementation

obj = {a:0};
Object.observe(obj, function(record) {
console.log(record);
});
            
obj.a = 1 // {type: 'updated', object: obj, name: 'a', oldValue: 1}
obj.b = true // {type: 'new', object: obj, name: 'b' }
delete obj.b // {type: 'delete', object: obj, name: 'b'}


Array slices are supported - read notifications are not
Change notifications are asynchronous

Why?


MVC internals - Remove dirty checking
Automatic Persistence - Make it possible
Form Constraints - Abstract them
Really just simpler APIs from observing


Instead of Proxies?

You can simulate observe with proxies
Proxies are more flexible, but fairly dangerous
Proxies require a lot more code
Observe is object identity safe
Caches need read notifications of proxies



Give me cake now!

Shared 0.2.0

An object sharing layer (using persistence)

var shared = require('shared')
var store = shared.createStore({host: 'mongohost'});
            
store.apply(function(db) {
  if (db.counter === undefined)
    db.counter = 0;
  else
    db.counter++;
}, function(err) {
   // Error handle
});


Each object is one document in MongoDB
References link objects in the DB
Garbage collection cleans dead objects up as needed

Design


Direct manipulation requires local caching of objects
The cache is garbage collected speratly from the database

apply() creates an operation set (observer style)
Uses handcrafted proxy code + dirty checking today
Native array ops hard to code but important for efficiency

Operation set translated into micro-transaction
readset - newset - changeset
Micro-transaction only commits if readset was current
Commit is a locked update, everything or nothing
Re-try on failure, watch for unintended side effects!

Work queue

function push(queue, item, cb) {
  store.apply(function(db) {
    if (db[queue] === undefined)
      db.queue = [];
    db.queue.push(item);
  }, function (err) {
    cb(err)
  }
}
        
function pop(queue, cb) {
  store.apply(function (db) {
  if (db[queue] !== undefined)
    return db[queue].shift();
  else
    return null;
  }, function (err, ret) {
    cb(err, ret);
  });
}
Sometimes beware Array op efficiency

Things to do


Cache overloading for large objects --
Best practices and/or partial object caching

Document stores vs Object Store --
Granularity of update instructions
Advanced feature support

Event emitting/triggers
Query capability 
Stored procedures
Housekeeping - Store Garbage Collection
  

Thanks


@hutkev
http://github.com/hutkev