couchdb mobile - from couch to 5k in 1 hour
DESCRIPTION
In this talk, I explain how to use CouchDB mobile to connect your iPhone or Android phone with a a remote ChouchDB to build a RunKeeper clone. The code for this talk is available at https://github.com/peterfriese/CouchTo5KTRANSCRIPT
44
CouchDB mobileFrom Couch To 5k in 1 Hour
Peter Friese / Stefan Reichert, Zühlke Engineering
Integrating Twitter in Your iOS 5 Apps
@peterfriese
[email protected]/peter
http://peterfriese.de
Peter Friese
Integrating Twitter in Your iOS 5 Apps
Stefan Reichert
@stefanreichert
xing.to/stefanreichert
http://blog.wickedshell.net/
What we will cover today
1
2
3
What is CouchDB?
Serious amount of demos!
Couch25K - A Runner’s App
1
What is CouchDB?
CouchDB is...
A NoSQL Database.
CouchDB is...
A NoSQL Database.
CouchDB is...Document-Oriented.
Schema-Free.
{! "name": "Peter",! "company": "Zühlke Engineering",! "email": "[email protected]",! "languages": [! ! "Objective-C",! ! "Java",! ! "C#",! ! "JavaScript"! ]}
CouchDB is...
Schema-Free.
Erlang-Powered.
Put anything you like into your CouchDB. Really.
CouchDB is...
Erlang-Powered.
RESTful.
CouchDB is...RESTful.
Map/Reduce (JavaScript).
Fully embraces HTTP verbs
GETPUTPOSTDELETE
CouchDB uses...
Map/Reduce (JavaScript).
http
://re
sear
ch.g
oogl
e.co
m/a
rchi
ve/m
apre
duce
.htm
l
CouchDB uses...
Map/Reduce (JavaScript).
{"ip": "212.23.45.12","traffic": “18278", date: “2012-03-11”},{"ip": "74.12.345.1","traffic": “345", date: “2012-03-11”},{"ip": "212.23.45.12","traffic": “112244", date: “2012-03-12”},{"ip": "212.23.45.12","traffic": “8657", date: “2012-03-13”},{"ip": "74.12.345.12","traffic": “12", date: “2012-03-12”},{"ip": "10.122.111.22","traffic": “122222", date: “2012-03-11”}
Input
CouchDB uses...
Map/Reduce (JavaScript).
212.23.45.12 1827874.12.345.1 345212.23.45.12 112244212.23.45.12 865774.12.345.1 1210.122.111.22 122222
Mapper212.23.45.12 18278212.23.45.12 112244212.23.45.12 8657
74.12.345.1 1274.12.345.1 345
10.122.111.22 122222
Input for Reducer
CouchDB uses...
Map/Reduce (JavaScript).
212.23.45.12 18278212.23.45.12 112244212.23.45.12 8657
74.12.345.1 1274.12.345.1 345
10.122.111.22 122222
Input for Reducer212.23.45.12 139179
74.12.345.1 357
10.122.111.22 122222
After Reduce
CouchDB features...
Robust Replication.
CouchDB features...
Robust Replication.
Phone
Laptop
Server
2
Demos
Calling Home
curl localhost:5984
{"couchdb":"Welcome","version":"1.1.0"}
Creating a New DB
curl -X PUT localhost:5984/helloworld
{"ok":true}
Deleting a Database
curl -X DELETE http://localhost:5984/helloworld
{"ok":true}
Creating a New Documentcurl -X POST -H "Content-Type: application/json" localhost:5984/helloworld -d @peter.json
{"ok":true,"id":"f8e42aaa4bc77124c286be13f000054e","rev":"1-4dc37117e0da26d9c50dc92d4cbb04cc"}
Curious? GET Your Document!curl http://localhost:5984/helloworld/f8e42aaa4bc77124c286be13f000054e
{ "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "[email protected]"}
Anatomy of a Document{ "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "[email protected]"}
UUID - very unique identifier
Anatomy of a Document{ "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "[email protected]"}
revision number
revision counter
hash (body, attachment, _deleted flag)
Welcome to Futon!
Admin interface for CouchDBCRUD for DocumentsManage ViewsManage Replication
3
Couch25K - A Runner’s App
Basic Idea
Phone IrisCouch
2-way 2-way
Phone
CouchDB Mobile
on your Ph
one?
CouchDB Mobile
CouchDB
Your app CouchCocoa lib Your app
CouchDB
Ektorp lib
CouchDB Mobile
SQlite
Your app CouchCocoa lib
SQlite
TouchDB lib
Your appEktorp lib
TouchDB lib
CouchDB Mobile
“TouchDB is a lightweight CouchDB-compatible database engine suitable for embedding into mobile or desktop apps. Think of it this way: If CouchDB is MySQL, then TouchDB is SQLite.” - Jens Alfke, Couchbase
Labs
Start a Local CouchCouchTouchDBServer *server =[CouchTouchDBServer sharedInstance];
NSAssert(!server.error, @"Error initializing TouchDB server: %@", server.error);
self.database = [serverdatabaseNamed:@"couch25k"];
NSError *error;if (! [self.database ensureCreated:&error]) {// raise errorself.connected = false;
}
Start a Local Couchtry {TDServer touchDBServer = new TDServer(filesDir);
!HttpClient httpClient = newTouchDBHttpClient(touchDBServer);
CouchDbInstance couchDBInstance = new StdCouchDbInstance(httpClient);
!CouchDbConnector couchDBConnector =couchDBInstance.createConnector(COUCH25K_DB,true);
! ...} catch (IOException e) {! Log.e(TAG, "Error starting TDServer", e);}
Trackpoints
{ "run": "run-peterfriese-19", "user": "peterfriese", "lon": "9.990512659959458", "time": "2012-03-24 07:39:27 +0000", "lat": "53.73176022303823"}
Saving a TrackpointNSDictionary *trackpointProperties = [NSDictionary dictionaryWithObjectsAndKeys: (...)
CouchDocument *trackpointDocument =[database untitledDocument];
RESTOperation* op = [trackpointDocument putProperties:trackpointProperties];
[op onCompletion: ^{if (op.error)NSLog(@"Couldn't save the new item");
}];[op start];
Saving a Trackpoint
public class TrackPoint extends CouchDbDocumentEktorp: JPA for CouchDB
Repository Supportpublic class TrackPointRepository extends ! CouchDbRepositorySupport<TrackPoint>
... so saving a TrackPoint is pretty easytrackPointRepository.add(trackPoint);
Sync with the Server
NSURL *url = [NSURL URLWithString:@"http://peterfriese.iriscouch.com/couch25k"];
[self.database replicateWithURL:url exclusively: YES];
Sync with the Server
ReplicationCommand commandPull = newReplicationCommand.Builder().source(COUCH25K_REMOTE_DB).target(COUCH25K_DB).continuous(true).build();
try {couchDBInstance.replicate(commandPull);
} catch (Exception exception) {Log.e(TAG, exception.getMessage(), exception);
}
Pull from Server
Sync with the Server
ReplicationCommand commandPush = newReplicationCommand.Builder().source(COUCH25K_DB).target(COUCH25K_REMOTE_DB).continuous(true).build();
try {couchDBInstance.replicate(commandPush);
} catch (Exception exception) {Log.e(TAG, exception.getMessage(), exception);
}
Push to Server
Demo
Display List of Runs (JavaScript)
function(doc) { emit(doc.run, 1);}
Map
run-1 1run-1 1run-1 1run-2 1run-2 1
function(keys, values) { return sum(values);}
Reduce
run-1 3run-2 2
Display List of Runs (Obj-C)
[design defineViewNamed: @"runs" mapBlock: MAPBLOCK({id run = [doc objectForKey:@"run"];if (run) emit(run, nil);
})
reduceBlock:REDUCEBLOCK({return [NSNumber numberWithInt:values.count];
})
Map
Reduce
Display List of Runs (Java)
new TDViewMapBlock() {public void map(Map<String, Object> doc, TDViewMapEmitBlock emitter) {if (doc.containsKey("run")) {emitter.emit(doc.get("run"), doc.get("_id"));
}}
}
Map
Display List of Runs (Java)
new TDViewReduceBlock() {public Object reduce(List<Object> keys, List<Object>values, boolean rereduce) {if (rereduce) {int sum = 0;for (Object object : values) {sum += (Integer) object;
}return sum;
}return values.size();
}};
(Re-) reduce
Filtering
Phone IrisCouch
sync 2-way
Phone 2
sync 2-way
Filter by name
Peter Stefan
Filtering
by_user:
function(doc, rq) { if(doc.user == rq.query.username) { return true;
} return false;
}
CouchDB
FilteringNSArray *replications = [self.database replicateWithURL:url exclusively: YES];
CouchPersistentReplication *from = [replications objectAtIndex:0];
from.continuous = YES;
from.filter = @"couch25k/by_user";NSDictionary *filterParams = [NSDictionary dictionaryWithObjectsAndKeys:@"peterfriese", @"username", nil];
from.query_params = filterParams;
FilteringMap<String, Object> queryParams =new HashMap<String, Object>();
queryParams.put("username", "stefanreichert");
ReplicationCommand commandPull = newReplicationCommand.Builder().source(COUCH25K_REMOTE_DB).target(COUCH25K_DB).continuous(true).filter("by_user").queryParams(queryParams).build();
try {! couchDBInstance.replicate(commandPull);} catch (Exception e) {! Log.e(TAG, exception.getMessage(), e);}
Maps, please!
Query Trackpoints by Run
mapBlock: MAPBLOCK({NSString *run = (NSString *)[doc objectForKey:@"run"];id time = [doc objectForKey:@"time"];NSMutableArray *key = [[NSMutableArray alloc] init];[key addObject:run];[key addObject:time];emit(key, doc);})
Map
[run-peter-1, 2012-03-23 10:10] {lat:..., lon:...}[run-peter-1, 2012-03-23 10:11] {lat:..., lon:...}[run-peter-2, 2012-03-26 20:05] {lat:..., lon:...}[run-peter-2, 2012-03-26 20:06] {lat:..., lon:...}[run-peter-2, 2012-03-26 10:07] {lat:..., lon:...}
Query Trackpoints by Run
CouchQuery *query = [[self.database designDocumentWithName: @"couch25k"] queryViewNamed: @"waypoints_by_run"];
CouchLiveQuery *livequery = [query asLiveQuery];
[livequery setStartKey:[NSArray arrayWithObjects:self.runKey, nil]];
[query setEndKey:[NSArray arrayWithObjects:self.runKey, @"ZZZ", nil]];
Query
Query Trackpoints by Run
new TDViewMapBlock() {public void map(Map<String, Object> document,TDViewMapEmitBlock emitter) {if (document.containsKey("run")) {document.get("run"), document.get("_id"));
}}
Map
Query Trackpoints by RunQueryViewQuery viewQuery = newViewQuery().designDocId("_design/TrackPoint").viewName("trackpoint_by_run").key(runId);
ViewResult result = db.queryView(viewQuery);List<TrackPoint> trackPoints = new ArrayList<TrackPoint>();
for (Row row : result.getRows()) {TrackPoint trackPoint = get(row.getValue());trackPoints.add(trackPoint);
}return trackPoints;
And finally...
http://josephta.me/about-joseph-tame/
... a Tribute to Steve Jobs GPX courtesy
of Joseph Tame - Thanks!
http://bit.ly/HbDRod
44
Relax!
Thanks!Peter FriesePrincipal Consultant
Zühlke Engineering GmbHAm Sandtorkai 6620457 Hamburg
+49 151 108 604 72Available for consulting,discussing all things mobile and frosty beverages
Stefan Reichert
Senior Software Engineer
Zühlke Engineering GmbH
Am Sandtorkai 66
20457 Hamburg
+49 173 961 43 36
@stefanreichert @peterfriese
PS: we’re hiring...