Hi Pablo,
Application feedback is very helpful for the async API. This is one
area that I have been the least convinced about (poor programmability
means dissatisfied users).
I am currently investigating the impacts of doing what is needed to
simplify the async API. At the very least, your proposal will add some
20 methods to a single interface - some applying to the database,
others to the store, and the rest to an index.
Additionally, we have to consider the case where more than one request
is concurrently made to the database.
I will get back to the WG with my findings. In the meanwhile, if you
have more findings to report, I would be glad to take them into
consideration.
Thanks,
Nikunj
On Nov 19, 2009, at 12:25 PM, Pablo Castro wrote:
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
Nikunj
http://o-micron.blogspot.com