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