[WebSimpleDB] Flatting APIs to simplify primary cases

We're busy creating experimental implementations of WebSimpleDB to both understand what it takes to implement and also to see what the developer experience looks like. 

As we started to write "application code" against the API (particularly the async one) the first thing that popped is the fact that you need two levels of nested callbacks for everything. While the current factoring of the API makes sense on the design board, it's kind of noisy in app code. For example:

// assume you already have a database opened in dbReq 
var html = "<ul>"; 
var storeReq = new ObjectStoreRequest(dbReq.database);
storeReq.success = function() {
    var cursorReq = new CursorRequest(storeReq.store);
    cursorReq.callback = function(key, cursor, value) {
        html += "<li>" + value.Name + "</li>";
    }
    cursorReq.onsuccess = function(r) {
        document.getElementById("output").innerHTML = html + "</ul>";
    }
    cursorReq.open();
}
storeReq.open();

One option that we would like to explore is to "flatten" the API, so most common methods are straight in the database class. This trades off some of the factoring in favor of usability for common cases using the async API.

The change would span a couple of aspects:

1. Move operations from object store interface and the index interface into the Database interface.

Accessing indexes and stores through specialized objects is problematic for the following reasons:
- It's always the case that we need to consider when objects are invalidated because something changes from underneath them, for example a schema change. So for example, if there is an explicit store object, then when the store is dropped we need to consider what is valid/invalid and what its failure points and modes are. By not having a standalone store object, we significantly reduce the "gotchas" to consider.
- From a usability perspective, it's simpler to work with a store in a single step, rather than having to open it first and then work with it (see patterns below with a single request and one DBRequest object).
- With no "two-step" access pattern, the API has one less level of asynchronicity, as effectively the table lookup + operation are atomic within the store. This also consolidates all operations with an async variant in a single interface (the Database), which is a great simplification for discoverability.

var html = "<ul>";
var request = asyncDb.forEachStoreObject("contacts", function(row) {
   html += "<li>" + row.Name + "</li>";
});
request.onsuccess = function(r) {
  document.getElementById("output").innerHTML = html + "</ul>"; }

In moving the operations, it's probably best to rename them to something more descriptive, so we can have for example 'getFromStore(storeName, key)' and 'getFromIndex(storeName, indexName, key)'. This also helps in that 'delete' won't collide with the Javascript keyword.

Note that the store and index interfaces are still around to provide metadata, but at this point they behave as simple read-only snapshots.

2. Generalize the use of DBRequest, add a 'result' member to it and have all asynchronous operations be initiated from a DatabaseAsync interface.

As a result of the previous changes, all operations that have an async counterpart should now exist on the DatabaseAsync interface. Rather than having multiple types of requests depending on the target object, it is possible to have operations on a DatabaseAsync interface that provide a uniform invocation and handling programming pattern.

This gives a nice pattern for understanding how a sync API maps to an async API.

So for example:

var record = db.getFromStore("store", key); // use record...

Becomes:

var request = asyncDb.getFromStore("store", key); request.onsuccess = function(req) {
  var record = req.result;
  // use record...
};

We could include more data in DBRequest or DBRequest.result as needed if in some cases a method produces more than just a simple result. Further specializatons of DBRequest (subtypes) are still possible in the future if we need to introduce special cases for specific operations.

Similarly, we would have something like asyncDb.forEachStoreObject() that queues a task to call a callback for each element in a store/index, potentially within a range if specified. The pattern scales well to all the other APIs present in db/store/index today.

If this seems like a good idea to folks, we'd be happy to write up a more complete version that articulates the tweaks across all the WebSimpleDB APIs to make this happen.

Regards,
-pablo

Received on Thursday, 19 November 2009 20:25:58 UTC