Thursday, September 29, 2011

Backbone.js Updates with Faye as the Persistence Layer

‹prev | My Chain | next›

I need to focus on writing Recipes with Backbone tonight, but I still hope to build some on the progress from last night. My efforts to switch to faye as the persistence layer in my Backbone.js calendar application have gone quite well to date. I can create, delete and read objects over faye at this point. So all that remains is update.

The Backbone view code from prior to the persistence layer switch still works, so I can still open an edit dialog to make changes:
Deciding that I should be more humble in my plea to the coding gods, I change the description from "dammit" to "please". Clicking OK seemingly updates the appointment on my calendar (mouseovers reveal the description). Even attempting to re-edit the appointment includes the updated description:
So is that it? Does it just work?

Of course not. I have not subscribed to the /calendars/udpate faye channel on my backend. My debug subscription in the client verifies that the message is being published to that channel:
So all I ought to need is to add a backend subscription to that channel. This follows a node.js / express.js pattern that has become familiar over the past few nights:
client.subscribe('/calendars/update', function(message) {
  // HTTP request options
  var options = {...};

  // The request object
  var req = http.request(options, function(response) {...});

  // Rudimentary connection error handling
  req.on('error', function(e) {...});

  // Write the PUT body and send the request
  req.write(JSON.stringify(message));
  req.end();
});
The pattern is to set HTTP options, here a PUT, to update the existing record:
  // HTTP request options
  var options = {
    method: 'PUT',
    host: 'localhost',
    port: 5984,
    path: '/calendar/' + message._id,
    headers: {
      'content-type': 'application/json',
      'if-match': message._rev
    }
  };
(the if-match is a CouchDB optimistic locking thing)

Next, I build the http request object which includes a response handler callback. This callback parses the JSON response from CouchDB and sends it back on the /calendars/changes channel:
  // The request object
  var req = http.request(options, function(response) {
    console.log("Got response: %s %s:%d%s", response.statusCode, options.host, options.port, options.path);

    // Accumulate the response and publish when done
    var data = '';
    response.on('data', function(chunk) { data += chunk; });
    response.on('end', function() {
      var couch_response = JSON.parse(data);
      client.publish('/calendars/changes', couch_response);
    });
  });
Last I send the message/record via the request object and close the request so that the CouchDB server knows that I have no more HTTP PUT data to send:
  // Write the PUT body and send the request
  req.write(JSON.stringify(message));
  req.end();
If all goes according to plan, the messages that I already know are being sent on /calendars/update will be seen by my server-side subscription, which will tell CouchDB to update the record and finally the browser will see the update on the /calendars/changes channel.

And that is exactly what happens. The PUT is logged as a successful HTTP 201 response from CouchDB:
Got response: 201 localhost:5984/calendar/66543e3457df7597f0e41764e500067c
{ ok: true,
  id: '66543e3457df7597f0e41764e500067c',
  rev: '3-243c4d7084fcdcb7c728917a73e94b97' }
And I even see that response back in the browser:
Nice!

That almost seems too easy. And sadly, it is. If I try to make another change on the same record, the CouchDB updates fail with a HTTP 409 / Document Conflict:
Got response: 409 localhost:5984/calendar/66543e3457df7597f0e41764e500067c
{ error: 'conflict',
  reason: 'Document update conflict.' }
This is because the revision ID that is stored in the Backbone model is now out of date. I need to take the revision returned from the first update and ensure that model becomes aware of it. Otherwise, CouchDB's optimistic locking kicks in, rejecting the update.

True to my word, I call it a night here. I will pick back up tomorrow solving this last mystery. Then, perhaps, some refactoring because this code is extremely soggy (i.e. not DRY).




Day #149

No comments:

Post a Comment