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
,-->r2ar1 -->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
- 882