Android auf der Couch (Mobile Technology 2013)

8
www.mobile360.de 1 | 2013 Mobile Technology CouchDB | ANDROID 035 von Peter Friese und Stefan Reichert Die grundlegenden Konzepte und Eigenschaften von CouchDB sind im ersten Artikel bereits ausführlich be- schrieben worden. An dieser Stelle soll ein kurzer Abriss genügen. CouchDB ist eine dokumentenorientierte No- SQL-Datenbank. Die Daten werden schemalos in Form von JSON-Dokumenten abgelegt, wobei die Kommuni- kation mit der Datenbank ausschließlich REST-basiert über HTTP stattfindet. Eines der wesentlichen Merkma- le von CouchDB ist die Unterstützung einer robusten Replikation. Diese Fähigkeit macht den Einsatz gerade im mobilen Umfeld interessant, wie im ersten Artikel deutlich geworden ist. Das dort beschriebene Lauf-App- Beispiel soll in diesem Artikel für Android nachempfun- den werden. CouchDB mobile – Android flavored Android auf der Couch NoSQL-Datenbanken erfreuen sich im Webumfeld zunehmender Beliebtheit, doch kann man sie auch sinnvoll auf mobilen Geräten einsetzen? Der erste Artikel „CouchDB mobile“ hat die NoSQL-Datenbank CouchDB im Zusammenspiel mit iOS beleuchtet. Dieser Artikel widmet sich dem Einsatz im Android-Umfeld. © iStockphoto.com/FreeSoulProduction © iStockphoto.com/-Mosquito-

description

NoSQL-Datenbanken erfreuen sich im Webumfeld zunehmender Beliebtheit, doch kann man sie auch sinnvoll auf mobilen Geräten einsetzen? Der erste Artikel „CouchDB mobile“ hat die NoSQL-Datenbank CouchDB im Zusammenspiel mit iOS beleuchtet. Dieser Artikel widmet sich dem Einsatz im Android-Umfeld.

Transcript of Android auf der Couch (Mobile Technology 2013)

Page 1: Android auf der Couch (Mobile Technology 2013)

www.mobile360.de 1 | 2013 Mobile Technology

CouchDB | ANDROID 035

von Peter Friese und Stefan Reichert

Die grundlegenden Konzepte und Eigenschaften von CouchDB sind im ersten Artikel bereits ausführlich be-schrieben worden. An dieser Stelle soll ein kurzer Abriss genügen. CouchDB ist eine dokumentenorientierte No-SQL-Datenbank. Die Daten werden schemalos in Form von JSON-Dokumenten abgelegt, wobei die Kommuni-

kation mit der Datenbank ausschließlich REST-basiert über HTTP statt� ndet. Eines der wesentlichen Merkma-le von CouchDB ist die Unterstützung einer robusten Replikation. Diese Fähigkeit macht den Einsatz gerade im mobilen Umfeld interessant, wie im ersten Artikel deutlich geworden ist. Das dort beschriebene Lauf-App-Beispiel soll in diesem Artikel für Android nachempfun-den werden.

CouchDB mobile – Android � avored

CouchDB mobile – Android � avored

Android auf der Couch

NoSQL-Datenbanken erfreuen sich im Webumfeld zunehmender Beliebtheit, doch kann man sie auch sinnvoll auf mobilen Geräten einsetzen? Der erste Artikel „CouchDB mobile“ hat die NoSQL-Datenbank CouchDB im Zusammenspiel mit iOS beleuchtet. Dieser Artikel widmet sich dem Einsatz im Android-Umfeld.

© iS

tock

phot

o.co

m/F

reeS

oulP

rodu

ctio

n

© iS

tock

phot

o.co

m/-

Mos

quito-

ptr
Typewritten Text
FA-273, 2013
Page 2: Android auf der Couch (Mobile Technology 2013)

036 ANDROID | CouchDB

www.mobile360.deMobile Technology 1 | 2013

Recapture – die ArchitekturZu Beginn wollen wir nochmal einen kurzen Blick auf die Architektur der App werfen (Abb. 1). Das Aufzeich-nen der Wegpunkte einer gelaufenen Strecke soll neben dem iPhone auch mit einem Android-Smartphone mög-lich werden. Der GPS-Sensor des Smartphones dient dabei zur Erfassung der jeweils aktuellen Position des Läufers. Da wir beim Laufen nicht von einer perma-nent verfügbaren Internetverbindung ausgehen können (z. B. könnte die Netzabdeckung lückenhaft, bzw. das Smartphone aufgrund hoher Roaming-Gebühren im Ausland vollständig of�ine sein), müssen die GPS-Daten zunächst lokal gespeichert werden, bevor sie zu einem geeigneten Zeitpunkt mit einem Server – in unserem Fall mit einer CouchDB in der Cloud – synchronisiert werden können. Die Synchronisierung mit einer zent-ralen CouchDB-Instanz hat nicht nur den Grund, die Daten für eine spätere Auswertung verfügbar zu ma-chen. Bei einer vorhandenen Internetverbindung und dadurch möglichen zeitnahen Updates wäre es durch-aus denkbar, die aktuelle Position des Läufers und die seiner Kontrahenten während eines Wettkampfes auf einer großen Kartendarstellung anzuzeigen. Bereits im vorangegangenen Artikel haben wir dazu eine Couch-DB-Instanz angelegt – entweder in der Cloud auf http://iriscouch.com oder lokal auf dem eigenen Rechner (da der eigene Rechner üblicherweise nicht öffentlich sicht-bar im Internet erreichbar ist, ist letztere Konstellation natürlich nur zu Entwicklungszwecken sinnvoll).

Um eine möglichst transparente Synchronisierung zwischen den lokal auf dem Smartphone gespeicherten Daten und den in der Cloud gespeicherten Daten zu er-möglichen, setzen wir auch auf dem Client CouchDB ein. CouchDB verfügt über einen sehr einfach zu ver-wendenden Replikationsmechanismus, der sich ideal für unsere Zwecke eignet: Lokal abgelegte Daten werden bei Verfügung einer Internetverbindung mit der Gegen-stelle (d.  h. der zentralen CouchDB-Instanz) abgegli-chen. CouchDB geht dabei ressourcenschonend vor und sendet immer nur Deltas. Wenn viele Läufer die App ein-setzen und somit sehr viele Datensätze (bzw. Dokumen-te) erzeugt werden, besteht prinzipiell die Möglichkeit,

dass es beim Synchronisieren zu Schlüsselkollisionen kommt. Dem entgeht CouchDB durch die Verwendung von UUIDs, sodass alle Schlüssel global eindeutig sind. Kollisionen, die sich ggf. durch gleichzeitiges Aktualisie-ren von ein und demselben Datensatz ergeben könnten, werden durch die Verwendung eines Revision Counters aufgelöst – es gewinnt zunächst immer der höchste Re-vision Counter. In unserem Fall kann es prinzipbedingt nicht zu Kon�ikten kommen, da einzig und allein die Smartphones neue Datensätze erzeugen und die Daten-sätze nachträglich nicht verändert werden.

Die Synchronisation der Daten erfolgt asymmetrisch: Alle auf dem Smartphone erfassten Wegpunkte werden zur zentralen Instanz übertragen. Im Gegenzug wer-den allerdings nur diejenigen Daten von der zentralen Instanz an die jeweiligen Smartphones übertragen, die dem Benutzer zugeordnet sind. Dieser Mechanismus dient einerseits dem Datenschutz, andererseits verfügen Smartphones nicht über den notwendigen Speicherplatz, um eine komplette Replikation der zentralen Datenbank mit den kompletten Daten von potenziell Tausenden von Benutzern zu fassen. Der von CouchDB bereitge-stellte Replikationsmechanismus [1] ist recht mächtig, dennoch einfach zu kon�gurieren, und ermöglicht es uns, die Applikation ohne großen Aufwand of�inefähig zu machen.

CouchDB auf AndroidIn Teil 1 des Artikels haben wir beschrieben, dass CouchDB auf iOS durch die schnittstellenkompatible TouchDB-Library implementiert werden kann. Erfreu-licherweise gibt es eine entsprechende Bibliothek auch für Android, sodass wir als Basis für diesen Artikel auf TouchDB for Android [2] zurückgreifen werden. Für den Einsatz von TouchDB sprechen die gleichen Ar-gumente, die wir bereits im ersten Teil erwähnt haben: Footprint und möglichst kurze Startzeit der Bibliothek. Der Android-Client kommuniziert wie gewohnt über ein REST-API mit der lokalen Datenbank. Möglich wird dies analog zum iPhone-Port durch ein NSURLProto-col, TouchDB registriert das URL-Schema touchdb.

TouchDB verwendet die auf dem Gerät zur Verfü-gung stehende SQLite-Datenbank. Die Dokumente werden als Blob unter der Dokument-ID gespeichert. Die in CouchDB üblicherweise in JavaScript geschrie-benen MapReduce-Funktionen werden hier nativ in Java geschrieben. Alternativ kann man auch JavaScript verwenden, die Funktionen werden dann mithilfe von Rhino interpretiert und ausgeführt. Zusätzlich zu den Dokumenten werden die Ergebnisse der Map-Funkti-on einer View in der SQLite-Datenbank abgelegt. Das beschleunigt die Zugriffe auf die Map-Ergebnis-Daten für die Reduce-Funktion. Anschließend wird die Map-Funktion jeweils für neue Dokumente ausgeführt.

TouchDB-Views sind versioniert, die gespeicherten Ergebnisse hängen an der jeweiligen Version der View, die direkt im Code angegeben ist. Verändert sich die View, dann muss im Code die Versionsnummer erhöht

Listing 1: Herstellen der Verbindung zu einer lokalen TouchDB-Instanz

...// TouchDB Server erstellenString filesDir = getFilesDir().getAbsolutePath();TDServer touchDBServer = new TDServer(filesDir);// Verbindung zu einer Datenbank aufbauenHttpClient httpClient = new TouchDBHttpClient(touchDBServer);StdCouchDbInstance couchDBInstance = new StdCouchDbInstance(httpClient);CouchDbConnector couchDBConnector = couchDBInstance.createConnector("couch25k", false);...

Page 3: Android auf der Couch (Mobile Technology 2013)

CouchDB | ANDROID 037

www.mobile360.de

werden. Die bereits gespeicherten Ergebnisse sind damit ungültig und werden beim nächsten Start der TouchDB gelöscht und aktualisiert.

Für die Verwendung der TouchDB verwenden wir die Ektorp-Bibliothek [3]. TouchDB liefert einen passenden Adapter, mit dem Ektorp mit ein paar wenigen Ausnah-men wie bei einer gewöhnlichen CouchDB verwendet werden. Dazu aber später mehr.

ProjektsetupZunächst wollen wir uns anschauen, wie man ein Pro-jekt mit TouchDB und Ektorp aufsetzen kann. Dazu wird die Eclipse-IDE und das dazu passende Android SDK verwendet. Folgende Schritte sind notwendig:

1. Neues, leeres Android-Projekt anlegen2. Im Projektverzeichnis einen Ordner /libs/ anlegen,

Bibliotheken dieses Ordners be�nden sich automa-tisch im Klassenpfad der neuen Android-App

3. TouchDB-Android klonen: git clone git://github.com/couchbaselabs/TouchDB-Android.git

4. Die Projekte TouchDB-Android und TouchDB-Android-Ektorp als Eclipse-Projekte in das IDE importieren

5. Kopieren des Inhalts der /libs/-Ordner beider Touch-DB-Projekte in den neu angelegten /libs/-Ordner des leeren Android-Projekts

6. Kopieren der Binary-Java-Archive der TouchDB-Projekte aus dem jeweiligen /bins/-Ordner (touchdb-android.jar und touchdb-android-ektorp.jar) in den neu angelegten /libs/-Ordner des leeren Android-Projekts (die Archive werden vom IDE automatisch erzeugt)

Tracken von GPS-WegpunktenDas Tracken der GPS-Daten stellt den zentralen An-wendungsfall der Lauf-App dar. Die Aufzeichnung von GPS-Daten erfolgt unter Android wie folgt: Die Plattform stellt als SystemService ein Objekt der Klas-se android.location.LocationManager bereit. Die Me-thode requestLocationUpdates(provider, minTime, minDistance, listener) bietet die Möglichkeit, einen LocationListener anzumelden. Bei Änderungen des Standorts innerhalb der de�nierten Parameter wird der registrierte Listener durch Aufruf der Methode onLocationChanged(location) benachrichtigt. Das Lo-cation-Objekt der Signatur beinhaltet die jeweils aktu-elle Position des Geräts.

Da das Ermitteln von Positionsdaten den Akku des Smartphones belastet, sollte der LocationListener also erst bei Bedarf registriert werden. Zudem sollte die Ab-fragefrequenz mithilfe der Parameter minTime und min-Distance ökonomisch sinnvoll gewählt werden.

Ablegen der Daten in der lokalen DatenbankDie Daten des Location-Objekts werden zunächst in ei-ner lokalen CouchDB abgelegt. Dafür benötigen wir als Erstes eine Verbindung zur Datenbank (Listing 1).

CouchDB-Dokumente verwaltenFür das Verwalten der Dokumente verwenden wir Ek-torp, was das Arbeiten mit einer CouchDB vereinfacht. Es bietet ein Mapping eines JSON-Dokuments auf Java-Objekte eines passenden Typs. Für die Konvertierung wird intern die Jackson-Bibliothek [4] genutzt. Somit er-übrigt sich das Arbeiten mit Key-Value-Paaren im Code,

Abb. 1: Architektur unserer Lauf-App

AnzeigeAnzeige

Page 4: Android auf der Couch (Mobile Technology 2013)

038 ANDROID | CouchDB

www.mobile360.deMobile Technology 1 | 2013

die Datenstrukturen können intuitiv als Java-Objekte verwendet werden.

Ektorp kann mit jeder beliebigen CouchDB verwen-det werden, es verwendet das REST-API der Datenbank. Praktischerweise beinhaltet der TouchDB-Android-Port eine Ektorp-HTTP-Client-Implementierung, mit der man Ektorp auch für den Zugriff auf die lokale Touch-DB kon�gurieren kann. Der TouchDBHttpClient nutzt für die Kommunikation das bereits erwähnte touchdb-URL-Schema der TouchDB. Die Wegpunkte eines Laufs sollen in unserer Lauf-App als Entität Trackpoint abge-bildet werden (Listing 2).

Ein Trackpoint beinhaltet dabei natürlich die Informa-tionen über Längen- und Breitengrad der aktuellen Positi-on. Darüber hinaus werden der aktuelle Name des Users, die ID des Laufs und der aktuellen Zeitpunkt gespeichert. Das Dokument ist damit inhaltlich kompatibel mit dem auf dem iPhone verwendeten Dokument. Auch auf dem Android-Smartphone werden diese Daten benötigt, um per MapReduce verschiedene Auswertungen, wie bei-spielsweise die Ermittlung aller Wegpunkte eines Laufs

oder alle Läufe eines Users, über die gesamte Datenmenge durchführen zu können. Sollte ein Benutzer sowohl ein iPhone als auch ein Android Device verwenden, schließt dies natürlich auch eventuell auf dem iPhone getrackte Daten ein, die über die später vorgestellte Replikation auf das Android-Smartphone synchronisiert wurden.

In Listing 2 ist zu sehen, dass die Klasse Trackpoint von der Klasse CouchDbDocument erbt. Damit kann mit einem Repository ein weiteres Ektorp-Feature genutzt werden. CouchDbDocuments können mithilfe eines Repositories verwaltet werden. Ein Repository kapselt die Zugriffe auf die Datenbank, die für das Arbeiten mit Objekten einer bestimmten Klasse notwendig sind. Die Basisklasse bietet von sich aus bereits die CRUD-Ope-rationen an und kann um zusätzliche Views erweitert werden. Zunächst sehen wir uns aber das Speichern von Trackpoints an.

Mit Repositories arbeitenDas Repository TrackpointRepository erbt von der Klasse CouchDbRepositorySupport<T>. Die Kontext-klasse T ist in diesem Fall die Klasse Trackpoint. Das Repository benötigt im Konstruktor einen CouchDB-Connector als Parameter, dessen Erzeugung in Listing 1 dargestellt ist. Das Speichern eines Trackpoints im LocationListener wird durch die auf dem Repository bereits existierende Methode add(trackpoint)trivial ab-gewickelt, wie in Listing 3 zu sehen ist.

Bei Positionsänderungen wird somit jeweils ein Trackpoint in der lokalen TouchDB abgelegt, solan-ge der Listener angemeldet ist. Das Tracking kann ein-fach durch das Abmelden des LocationListeners vom LocationManager beendet werden. Mit der Methode removeUpdates(listener) wird der zuvor angemeldete Lo-cationListener wieder entfernt.

Daten für KartenWie auch auf dem iPhone soll es möglich sein, sich die gelaufene Strecke auf einer Karte anzusehen. Alle Weg-punkte des jeweiligen Laufs müssen dafür aus der lokalen Datenbank geladen werden. Die Karte sollte die Weg-punkte dann in der richtigen zeitlichen Reihenfolge als Pfad (wenn Anfangs- und Endpunkt nicht übereinstim-men) bzw. als Polygon (wenn Anfangs- und Endpunkt übereinstimmen) darstellen.

Abfragen auf der CouchDB werden mithilfe von Map Reduce [5] implementiert. Wie schon im ersten Teil des Artikels beschrieben, arbeitet MapReduce mit einem zweistu�gen Verfahren. Zunächst werden die Dokumente mithilfe der Map-Funktion gruppiert. Die Funktion behandelt dabei genau ein einziges Doku-ment. Ist das Dokument für die Abfrage relevant, dann erzeugt die Map-Funktion ein Daten-Tupel für das Do-kument. Das Ergebnis der Map-Funktion ist also eine Menge von Tupeln. Diese Tupel werden von der Re-duce-Funktion ausgewertet. Beide Funktionen, sowohl Map als auch Reduce, müssen seiteneffektfrei sein, d. h. die Funktionen müssen für ein Dokument immer das

Abb. 2: Übersicht der Läufe Abb. 3: Detailansicht eines Laufs

Listing 2: Die Entität Trackpoint

public class Trackpoint extends CouchDbDocument implements Comparable<Trackpoint> {

private String user; private String run; private double lon; private double lat; private Date time;

...}

Einfach online bestellen unter www.javamagazin.de/abooder telefonisch unter +49 (0) 6123 9238-239 (Mo–Fr, 8–17 Uhr)

Jetzt

Premium-

Angebot

sichern!

Das Magazin für Java-Profi s!

Ihre Vorteile auf einen Blick :

Frei-Haus-Lieferung des Print-Magazins

Alle Ausgaben online immer und überall verfügbar

Offl ine-PDF-Export inkl. Volltextsuche

HQ in HD auch auf dem iPad erhältlich

Page 5: Android auf der Couch (Mobile Technology 2013)

CouchDB | ANDROID 039

www.mobile360.de 1 | 2013 Mobile Technology

gleiche Ergebnis liefern, unabhängig von anderen Ein-�ussfaktoren.

Designdokumente und Views anlegenDie Views bzw. die Zugriffe auf Views werden im Re-pository der jeweiligen Entität de�niert. Ektorp erlaubt eine annotationsbasierte De�nition von Views auf Basis von JavaScript [6] über das REST-API der CouchDB.

Die TouchDB für Android unterstützt diese Funktion grundsätzlich, allerdings ist dafür das Projekt Touch-DB-Android-JavaScript erforderlich. Unsere Lauf-App de�niert die Views alternativ in Java und verwendet Ek-torp lediglich, um auf die Views zuzugreifen (Listing 4).

Die für die Erzeugung der View benötigte TDData-base kann einfach mithilfe des in Listing 1 dargestellten TDServers erzeugt werden.

Listing 3: Das Trackpoint Repository...private TrackpointRepository trackpointRepository = new TrackpointRepository(couchDBConnector);...@Overridepublic void onLocationChanged(Location location) { Trackpoint trackpoint = new Trackpoint(); trackpoint.setRun(runTitle); trackpoint.setUser(user); trackpoint.setLat(location.getLatitude()); trackpoint.setLon(location.getLongitude()); trackpoint.setTime(new Date());trackpointRepository.add(trackpoint);}...

Listing 4: View TrackpointsByRun

private void createTrackpointsByRunView(TDDatabase database) { String viewName = "Trackpoint/trackpoint_by_run" TDView view = database.getViewNamed(viewName); view.setMapReduceBlocks(new TDViewMapBlock() {

@Override public void map(Map<String, Object> document, TDViewMapEmitBlock emitter) { if (document.containsKey("run")) { String runId = document.get("run"); emitter.emit(runId, document.get("_id")); } } }, null, "1.0");}

AnzeigeAnzeige

Page 6: Android auf der Couch (Mobile Technology 2013)

040 ANDROID | CouchDB

www.mobile360.deMobile Technology 1 | 2013

Die MapReduce-Funktionen einer CouchDB werden üblicherweise selbst in der Datenbank in so genannten „Designdokumenten“ gespeichert. Bei der Verwendung TouchDB hingegen müssen die Designdokumente bei jedem Start neu angelegt werden. Die Methode aus Lis-ting 4 legt die View (bzw. die MapReduce-Funktion) trackpoint_by_run im Designdokument _design/Track-point an. Passenderweise liegt die Methode im Track-pointRepository aus Listing 3 und wird bei jedem Start der Lauf-App durchlaufen.

Implementieren des Map-BlocksDie Map-Funktion wird als anonyme Klasse implemen-tiert und implementiert das Interface TDViewMap-Block. Die Signatur der Funktion liefert zum einen das Dokument als Map und einen TD-ViewMapEmitBlock. Der Block baut nun ein Tupel in Form [Name des Laufs, Dokument-ID] auf und übergibt es mit dem Aufruf der emit()-Methode an den TDView-MapEmitBlock. Das Resultat ist eine View, die alle IDs von Doku-menten eines Laufs listet. Tabelle 1 zeigt einen Ausschnitt der durch die Map-Funktion erzeugten Struktur.

Der View-Inhalt wird, wie ein-gangs schon erwähnt, in der SQLite-Datenbank gespeichert. So muss die Map-Funktion für ein Dokument nur ein einziges Mal durchgeführt werden. Bei späteren Zugriffen wird der gespeichert Werte verwendet und reduziert so die Laufzeit. Sollte sich das Dokument ändern, dann wird die Map-Funktion erneut für das Dokument aufgerufen und der gespeicherte Wert aktualisiert.

Zugriff auf ViewsDer Zugriff auf den Inhalt von Views ist mit Ektorp sehr einfach zu realisieren. Das TrackpointRepository wird um eine Methode �ndByRun(String) erweitert. Die Methode liefert die zeitlich sortierte Menge an Wegpunkten des Laufs als SortedSet, wie in Listing 5 zu sehen ist.

@Overridepublic int compareTo(Trackpoint another) { return time.compareTo(another.getTime());}

Das Au�ösen eines Trackpoints anhand der Dokument-ID wird hier an die Methode get(String) des Reposito-ries delegiert. Alternativ kann Ektorp das Ergebnis mit der Methode queryView(viewQuery, Class<T>) auch direkt in Trackpoints konvertieren. Ektorp müsste dafür vorde�nierte Designdokumente (so genannte Standard-

designdokumente) des TrackpointRepositories anlegen, diese sind allerdings in Form von JavaScript de�niert. Daher wäre hier wiederum das Projekt TouchDB-An-droid-JavaScript erforderlich.

Die Wegpunkte eines Laufs werden so als Java-Objekt in chronologischer Reihenfolge zurückgeliefert. Eine Android-MapView kann sie jetzt sequenziell als Eck-punkte eines Polygons auf einer Karte darstellen.

Übersicht der LäufeDer Benutzer soll die Möglichkeit haben, sich die Liste der absolvierten Läufe anzuschauen. Die Daten dafür be�nden sich bereits im System, es stellt sich nun die Frage, wie wir eine entsprechende Verdichtung auf die Läufe erreichen. Die bisherige Abfrage zielt auf die Weg-

punkte eines einzelnen Laufs ab; auf den ersten Blick sieht es also so aus, als ob eine zweite View benötigt wird.

Allerdings können wir für die Daten der Übersicht die View trackpoint_by_run wiederver-wenden. Die Ektorp-ViewQuery bietet eine Gruppierung für alle Ergebnisse eines Schlüssels an, im Prinzip eine sehr einfache Re-duce-Funktion. Das Ergebnis der Abfrage liefert dann nicht mehr ein Ergebnis pro Dokument (Tabelle 1), sondern die Menge an Ergebnissen pro Schlüssel. Die zu dem Schlüssel passenden Dokument-IDs werden, wie in Tabelle 2 als Auszug zu sehen ist, als Menge zurückgegeben.

Das TrackpointRepository erhält so eine weitere Methode �ndRuns(), die die Menge von Läufen zurückgibt. Die Metho-de ist in Listing 6 abgebildet. Anstelle des Suchkriteriums .key(String) (Listing 5) wird der

ViewQuery die Anweisung .group(true) übergeben. Al-ternativ bestünde die Möglichkeit, eine weitere View an-zulegen, die die Dokumente entsprechend indiziert und verdichtet. An dieser Stelle sei nochmal auf das Buch „The De�nitive Guide To CouchDB“ [7], [8] hingewiesen. Hier gibt es detaillierte Informationen zu MapReduce, Re-Reduce und weiteren Themen rund um die CouchDB.

SynchronisationMit der Abfrage für die Übersicht an Läufen sind die Tei-le der App vollständig, die auf dem Andoid-Smartphone laufen. Die Einstiegsseite (Abb. 2) bildet die eben genann-te Übersicht. Mit der Auswahl eines Laufs werden die einzelnen Wegpunkte des Laufs in Form von Trackpoint-Objekten ermittelt. Anhand der Trackpoints können dann Informationen wie zurückgelegte Distanz, Dauer

Abb. 4: Kartenansicht eines Laufs

Page 7: Android auf der Couch (Mobile Technology 2013)

CouchDB | ANDROID 041

www.mobile360.de 1 | 2013 Mobile Technology

des Laufs oder durchschnittliche Geschwindigkeit dar-gestellt werden (Abb. 3). Zusätzlich kann sich der User seinen Lauf auf der Karte anschauen (Abb. 4).

Ein ganz wesentlicher Teil der App fehlt hingegen noch: Die Daten der lokalen CouchDB sollen mit einer Instanz in der Cloud synchronisiert werden. Das Ganze hört sich aber weitaus komplizierter an, als es in Wahrheit ist.

AsynchronitätBevor wir uns dem Code widmen, soll hier auf den asynchronen Charakter der Synchronisation hingewie-sen werden. Alle Wegpunkte aus der lokalen Datenbank werden in die Cloud repliziert. Hingegen werden ledig-lich die Wegpunkte des Benutzers aus der Cloud auf das Android-Smartphone transportiert. Zum einen macht dies aus Gründen des Datenschutzes Sinn, zum anderen wäre die gesamte Datenmenge allein aus Platzgründen eine Herausforderung für die lokale Datenbank des Smartphones. Die entsprechende Synchronisationsrich-tung wird daher mit einem Filter kon�guriert.

Eine CouchDB in der CloudWie schon im ersten Teil des Artikels beschrieben, wird für die Synchronisation eine CouchDB-Instanz benötigt. Sollten Sie sich für die iPhone-App aus dem ersten Teil des Artikels bereits lokal eine CouchDB installiert [9] oder ein Iris-Couch-Konto angelegt haben [10], so können Sie diese Instanz weiterverwenden. Ansonsten müssen Sie sich an dieser Stelle für eine der beiden Alternativen entscheiden, da eine CouchDB wie gesagt notwendig ist.

Einrichten des FiltersDieser Absatz ist für Sie nur interessant, wenn Sie eine neue CouchDB angelegt haben. Sollten Sie die CouchDB bereits für das iPhone-Beispiel kon�guriert haben, dann können Sie den Abschnitt überspringen. Zunächst benöti-gen wir eine Datenbank auf der CouchDB, diese kann ent-weder über curl -X PUT http://127.0.0.1:5984/couch25k oder über Futon, das integrierte Browser-Admin-UI einer jeden CouchDB, angelegt werden. Futon erreichen Sie im-mer unter dem URL http://<host>:5984/_utils/.

Bei der Kon�guration der Replikation kann für jede Richtung eine Filterfunktion referenziert werden, die für die Synchronisation angewendet wird. Die Filterfunktion muss dabei jeweils auf der Seite der Quelle de�niert sein, so werden nur die auf dem Filter passenden Dokumente übertragen. Eine De�nition auf der Zielseite wäre sehr inef�zient, da zunächst alle Dokumente übertragen wer-den müssten und erst auf der Zielseite über die Relevanz des Dokuments geurteilt werden könnte. Die Filterfunk-tionen werden in JavaScript formuliert und für unsere Lauf-App im Designdokument _design/couch25k in der CouchDB gespeichert. Listing 7 zeigt die für die Synchro-nisation verwendete Filterfunktion by_user.

Die Funktion vergleicht den Wert des Feldes user ei-nes Dokuments mit dem Request-Parameter username der Abfrage. Ist der Filter aktiv, dann werden nur Doku-mente repliziert, auf die diese Bedingung zutrifft.

Listing 7: Die Filterfunktion für die Synchronisation{ "_id": "_design/couch25k", "_rev": "10-fe6de55bfd887a6c777a0a712cc0b6b3", "language": "javascript", "filters": { "by_user": "function(doc, req) { return doc.user == req.query.username; }" }}

Kon�gurieren der ReplikationDas Kon�gurieren der Replikation ist wie schon er-wähnt sehr einfach. Ektorp bietet hierfür die Klasse ReplicationCommand. Der Command wird mit einer

Listing 5: Zugriff auf die Wegpunkte eines Laufspublic SortedSet<Trackpoint> findByRun(String runId) { ViewQuery viewQuery = new ViewQuery() .designDocId("_design/Trackpoint") .viewName("trackpoint_by_run") .key(runId); ViewResult result = db.queryView(viewQuery); Set<Trackpoint> trackpoints = new TreeSet<Trackpoint>(); for (Row row : result.getRows()) { Trackpoint trackpoint = get(row.getValue()); trackpoints.add(trackpoint); } return trackpoints;}

Listing 6: Zugriff auf die Liste von Läufenpublic SortedSet<String> findRuns() { ViewQuery viewQuery = new ViewQuery() .designDocId("_design/Trackpoint") .viewName("trackpoint_by_run") .group(true); ViewResult result = db.queryView(viewQuery); SortedSet<String> runIds = new TreeSet<String>(); for (Row row : result.getRows()) { runIds.add(row.getKey()); } return runIds;}

Key Value

"stefan-run-1" "b0bc34e2-dc0d-4e21-8b1f-d57edcc67607"

"stefan-run-1" "b1d0be12-32b2-466d-a2bb-a118e458a764"

"stefan-run-1" "b3ea49c7-2dd2-4e15-8b46-6bc6ee00915a"

"stefan-run-1" "b4f152ea-bd00-41ca-ab4f-6fa69629ec84"

"stefan-run-1" "b608a7ef-894b-411b-a645-5f8fc0a66f61"

Tabelle 1: Ergebnis der Map-Funktion

Page 8: Android auf der Couch (Mobile Technology 2013)

042 ANDROID | CouchDB

www.mobile360.deMobile Technology 1 | 2013

Quelle und einem Ziel kon�guriert. Es können entweder URLs für entfernte Datenbanken oder Namen von lokal zur Verfügung stehenden Datenbanken verwendet wer-den. Ein ReplicationCommand behandelt somit immer eine einzelne Replikationsrichtung. Der Upstream, also das Übertragen der lokalen Daten in die Cloud, wird wie in Listing 8 kon�guriert.

Als Quelle wird der Name der lokalen Datenbank couch25k angegeben, das Ziel ist der URL der Couch-DB in der Cloud. Die entgegengesetzte Richtung ist un-wesentlich komplizierter, da hier der Filter kon�guriert wird (Listing 9).

Die Replikation verweist auf den in der CouchDB de-�nierten Filter by_user im Designdokument couch25k. Die Filterkriterien werden als Map übergeben und in der Filterfunktion mit ausgewertet. Nur der Vollständigkeit

halber sei gesagt, dass auch Filter für die andere Repli-kationsrichtung unterstützt werden [11].

Die Synchronisation läuft im Hintergrund und gleicht die Dokumente beider Datenbanken zeitnah ab. Bei be-stehender Verbindung zum Internet werden so die lokalen Daten automatisch in die zentrale Datenbank repliziert.

FazitIm zweiten Teil des Artikels haben Sie gesehen, wie CouchDB auch auf Android-Geräten eingesetzt werden kann. TouchDB für Android erlaubt es, auch für diese Plattform auf einfache Art und Weise of�inefähige Apps zu entwickeln, die, wenn möglich, im Hintergrund loka-le Daten und Daten in der Cloud synchronisieren. Dabei besteht auch hier die Möglichkeit, die Synchronisation mittels Filter ökonomisch und sinnvoll zu kon�gurieren, und damit den besonderen Gegebenheiten auf mobilen Endgeräten Rechnung zu tragen.

Hervorzuheben ist, dass es die unterschiedlichen TouchDB-Ports erlauben, Apps für unterschiedliche Plattformen mit gleicher Architektur zu entwickeln. Die erfassten Dokumente sind dabei weitgehend kompatibel zueinander. Bei der Entwicklung der App können so alle Vorteile der jeweiligen Plattform voll ausgenutzt werden.

Stefan Reichert, Diplom-Wirtschaftsinformatiker (FH), arbeitet als Software Engineer bei Zühlke Engineering in Hamburg. Er beschäf-tigt sich seit mehreren Jahren mit verteilten Java-Enterprise-Anwen-dungen, serviceorientierten Architekturen, Eclipse und Eclipse RCP. Seit einiger Zeit begeistert ihn die Entwicklung von Apps mit An-

droid. Darüber hinaus ist er Buchautor und verfasst regelmäßig Fachartikel.

[email protected], http://www.wickedshell.net/blog

Peter Friese arbeitet als Software Architect und Principal Consul-tant für Zühlke Engineering. Seine Schwerpunkte liegen auf der Entwicklung von plattformübergreifenden mobilen Lösungen (u. a. für iOS, Android, Windows Phone) sowie der modellgetriebenen Entwicklung (MDSD). Peter bloggt auf http://www.peterfriese.de.

@peterfriese.

Key Value

"stefan-run-1" {"b0bc34e2-dc0d-4e21-8b1f-d57edcc67607","b1d0be12-32b2-466d-a2bb-a118e458a764","b3ea49c7-2dd2-4e15-8b46-6bc6ee00915a","b4f152ea-bd00-41ca-ab4f-6fa69629ec84","b608a7ef-894b-411b-a645-5f8fc0a66f61"}

Tabelle 2: Ergebnis der Map-Funktion mit Gruppierung

Listing 8: Die Synchronisation in die Cloud

ReplicationCommand push = new ReplicationCommand.Builder() .source("couch25k") .target("http://stefanreichert.iriscouch.com:5984/couch25k") .continuous(true) .build();try { couchDBInstance.replicate(push);} catch (Exception exception) { Log.e(TAG, exception.getMessage(), exception);}

Listing 9: Die Synchronisation aus die Cloud mit Filter

Map<String, Object> queryParams = new HashMap<String, Object>();queryParams.put("username", "stefan");

ReplicationCommand pull = new ReplicationCommand.Builder() .target("couch25k") .source("http://stefanreichert.iriscouch.com:5984/couch25k") .filter("couch25k/by_user") .queryParams(queryParams) .continuous(true) .build();try { couchDBInstance.replicate(pull);} catch (Exception exception) { Log.e(TAG, exception.getMessage(), exception);}

Links & Literatur

[1] http://guide.couchdb.org/editions/1/en/replication.html

[2] https://github.com/couchbaselabs/TouchDB-Android

[3] http://www.ektorp.org

[4] http://jackson.codehaus.org

[5] Jeffrey Dean and Sanjay Ghemawat: „MapReduce: Simpli�ed Data Processing on Large Clusters“: http://research.google.com/archive/ mapreduce.html

[6] http://ektorp.org/tutorial.html#d0e105

[7] J.Chris Anderson, Jan Lehnardt, Noah Slater: „CouchDB: The De�nitive Guide“, O’Reilly Media 2010

[8] http://guide.couchdb.org/

[9] http://wiki.apache.org/couchdb/Installation

[10] http://www.iriscouch.com

[11] https://github.com/couchbaselabs/TouchDB-Android/wiki/TouchDB-Ektorp-Replication