Offline First with CouchDB

Offline = Error (2014)

Offline = Mode (2015)

Slow ~= Offline

Read/write on any device

Offline Flow

Credit: Marcel Kalveram (http://de.slideshare.net/MarcelKalveram/offline-first-the-painless-way)

+

JSON Docs + HTTP API

{ 
  "_id": "saigon-opera-house",
  "name": "Saigon Opera House",
  "lat": 106.78,
  "lon": 10.75,
  "type": "sight",
  "hours": {
    "monday": {
      "start": "0900",
      "end": "2300"
    }
  }
}
GET http://couchserver/my_tripformi/_all_docs?include_docs=true

{
  "total_rows": 1,
  "offset": 0,
  "rows": [
    {
      "id": "saigon-opera-house",
      "key": "saigon-opera-house",
      "doc": {
        "_id": "saigon-opera-house",
        "name": "Saigon Opera House",
        "lat": 106.78,
        "lon": 10.75,
        "type": "sight"
        "hours": {
          "monday": {
            "start": "0900",
            "end": "2300"
          }
        }
      }
    }
  ]
}
}
function(doc) {
  emit(doc.type, doc.name);
}

GET http://couchserver/my_tripformi/_design/places/_view/by_type?key=city

Eventual Consistency

Replication!

var localDB = new PouchDB('my_tripformi_local');

localDB.put({ 
  "_id": "saigon-opera-house",
  "name": "Saigon Opera House",
  "lat": 106.78,
  "lon": 10.75
});

localDB.replicate.to('http://couchserver/my_tripformi_remote');

Insert + replicate

Local

Remote

Replicate

curl -H 'Content-Type: application/json' \
            -X POST http://coucherver/my_tripformi \
            -d '{ 
  "_id": "saigon-notredame-basilica",
  "name": "Saigon Notre Dame Basilica",
  "lat": 106.78,
  "lon": 10.75
}'

Direct insert

Remote

Shell

POST

var localDB = new PouchDB('my_tripformi_local')
var remoteDB = new PouchDB('http://couchserver/my_tripformi_remote')

localDB.sync(remoteDB).on('complete', function () {
  // yay, we're in sync!
}).on('error', function (err) {
  // boo, we hit an error!
});

Sync

POST /_replicate HTTP/1.1 
  {
    "source":"my_tripformi_local",
    "target":"http://couchserver/my_tripformi_remote"
  }

Local

Remote

Replicate

Changes feed

GET http://couchserver/my_tripformi/_changes
{"results":[{
    "seq":1,
    "id":"place-added",
    "changes":[{"rev":"1-967a"}]
  }, {
    "seq":3,
    "id":"place-updated",
    "changes":[{"rev":"2-7051"}]
  }, {
    "seq":5,
    "id":"place-deleted",
    "changes":[{"rev":"2-eec2"}],"deleted":true
  } ], 
"last_seq":5}

Changes feed (cont.)

GET http://couchserver/my_tripformi/_changes?since=3
{"results":[{
    "seq":5,
    "id":"place-deleted",
    "changes":[{"rev":"2-eec2"}],"deleted":true
  } ], 
"last_seq":5}

since_seq=3

seq=5

Versioning

PUT http://couchserver/my_tripformi/saigon-opera-house
{
    "title": "Saigon Opera House (NHÀ HÁT)"
}
{"error":"conflict","reason":"Document update conflict."}
curl -X PUT http://127.0.0.1:5984/tripformi/saigon-opera-house -d '{
    "_rev":"1-2902191555"
    "title": "Saigon Opera House (NHÀ HÁT)"
}'
PUT http://couchserver/my_tripformi/saigon-opera-house
{
    "_rev":"1-2902191555"
    "title": "Saigon Opera House (NHÀ HÁT)"
}

insert or POST

node

Conflict

_id: saigon-opera-house

rev: 1-aaa

name: Saigon Opera House

_id: saigon-opera-house

rev: 1-aaa

name: Saigon Opera House

_id: saigon-opera-house

rev: 2-bbb

name: Nhà hát Lớn thành

_id: saigon-opera-house

rev: 2-ccc

name: Saigon Opera House (Nhà hát Lớn thành)

_id: saigon-opera-house

rev: 2-ccc

name: Saigon Opera House (Nhà hát Lớn thành)

_id: saigon-opera-house

rev: 2-bbb

name: Nhà hát Lớn thành

replicate

replicate

Conflict (cont.)

Winning/Loosing

      ,--> r2a
    r1 --> r2b
      `--> r2c

r2a

r2b

r2c

Conflict (cont.)

Resolving

$ GET http://$COUCH_SERVER/my_tripformi/saigon-opera-house?conflicts=true

{"_id":"saigon-opera-house","_rev":"2-ccc","streetAddress":"Saigon Opera House (Nhà hát Lớn thành)",
"_conflicts":["2-aaa","2-bbb"]}
$ GET http://$COUCH_SERVER/my_tripformi/saigon-opera-house?rev=2-aaa

rev: 2-aaa

rev: 2-bbb

rev: 2-ccc

Further reading

  • CouchDB
    • Document validation
    • Indexing
    • Client UI (Futon, Fauxton)
    • Security
    • Attachments
    • Hosting (iriscouch, cloudant)
    • Offline Frameworks (hoodie)
    • Geospatial queries
  • PouchDB
    • NodeJS (LevelUp)
    • PouchDB Server
    • Native for iOS, Android

Offline = Feature

Thank you!

Herve Roussel

 

✈ hroussel@tripformi.com

✈ @hvroussel

✈ linkedin.com/in/hroussel

✈ angel.co/hroussel
✈ slideshare.com/hroussel

Offline First with CouchDB

By Hervé Roussel

Offline First with CouchDB

  • 879