• Like

Big iOS Data - NoSQL auf iOS devices: Mobile Technology3/2012

  • 502 views
Uploaded on

NoSQL auf iOS-Devices …

NoSQL auf iOS-Devices
Big iOS Data
NoSQL-Datenbanken erfreuen sich im Webumfeld zunehmender Beliebtheit, doch kann man sie auch sinnvoll auf mobilen Geräten einsetzen? In diesem Artikel wollen wir uns anhand eines Beispiels die NoSQL-Datenbank CouchDB im Zusammenspiel mit iOS ansehen.

Fazit
In diesem Artikel haben Sie gesehen, wie CouchDB eingesetzt werden kann, um auf einfache Art und Weise eine asymmetrische Synchronisierung zwischen den Datenbeständen einer mobilen Anwendung und einer zentralen Anwendung zu implementieren. Insbesondere für Systeme, die zur Erfassung von großen Datenmengen gedacht sind, ist CouchDB durch seine Architektur eine gute Wahl. In vielen Fällen kommt man Dank des RESTbasierten Datenbank-APIs sogar ohne Application-Server aus. Wie kurz angedeutet wurde, kann CouchDB mithilfe von CouchApp sogar als Application-Server für Web-Apps dienen. In einem Folgeartikel werden wir auf den Einsatz von CouchDB auf Android eingehen und das Thema CouchApps vertiefen.

Artikel von Peter Friese in der Mobile Technology 3/2012.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
502
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
7
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. CouchDB mobile | iOS 037 NoSQL auf iOS-Devices Big iOS Data NoSQL-Datenbanken erfreuen sich im Webumfeld zunehmen- der Beliebtheit, doch kann man sie auch sinnvoll auf mobilen Geräten einsetzen? In diesem Artikel wollen wir uns anhand eines Beispiels die NoSQL-Datenbank CouchDB im Zusam- menspiel mit iOS ansehen.www.mobile360.de 3 | 2012 Mobile Technology
  • 2. 038 iOS | CouchDB mobile von Peter Friese ganz unerheblichen Datenvolumen zu rechnen ist. Darü- ber hinaus ergeben sich durch das Beispiel einige zusätz- CouchDB ist eine Open-Source-NoSQL-Datenbank, die liche Probleme, die einer Lösung bedürfen. über einige interessante Eigenschaften verfügt, die eine Verwendung in mobilen Projekten besonders geeignet Architektur unseres Beispiels erscheinen lassen. Eine der wesentlichen Eigenschaften Werfen wir nun einen Blick auf die Architektur unserer ist die durchgängige Verwendung von Webprinzipien: Lauf-App: Die Erfassung der gelaufenen Strecke soll mit die Interaktion mit CouchDB kann vollständig über einem Smartphone erfolgen (zunächst iPhone, später HTTP erfolgen – das gesamte API ist REST-basiert und auch Android). Je nachdem wo die Laufstrecke liegt, ist kann im Prinzip mittels curl-Kommandos gesteuert wer- vielleicht keine permanente Verbindung zum Internet den. Wem das zu kryptisch ist, kann auf das browser- möglich, daher ist eine lokale Speicherung der aufge- basierte Administrations-Frontend Futon ausweichen zeichneten GPS-Daten unerlässlich. Um spätere Auswer- (Abb. 1). tungen zu ermöglichen, sollen die Daten aber auf jeden Anders als relationale Datenbanken speichert Couch- Fall auch in einer zentralen Datenbank in der Cloud ab- DB Daten und Relationen nicht in Tabellen. Stattdessen gelegt werden. Sobald das Smartphone also wieder mit werden die Daten schemalos in JSON-basierten Doku- dem Internet verbunden ist, müssen die in der lokalen menten abgespeichert. Abfragen werden in JavaScript Datenbank gespeicherten Daten mit der zentralen Da- formuliert und nutzen das MapReduce-Modell [1]. tenbank synchronisiert werden. CouchDB ist übrigens eine Abkürzung und steht für Diese Anforderungen können wir mit einer recht ein- „Cluster of unreliable Commodity Hardware“, was fachen Architektur umsetzen: Die beim Laufen aufge- auf eine weitere Kerneigenschaft hinweist: CouchDB ist zeichneten GPS-Koordinaten werden in einer lokalen für die Implementierung von verteilten Datenbanksys- CouchDB-Instanz auf dem Smartphone gespeichert. Um temen geschaffen worden und zeichnet sich durch ein eine zentrale Auswertung zu ermöglichen, installieren sehr einfaches aber dennoch effizientes inkrementelles wir auf einem Server im Web eine weitere CouchDB-Ins- Replikationsverfahren aus. Eventuell bei der Replikati- tanz. Diese zentrale CouchDB dient den mobilen Clients on auftretenden Konflikte werden durch Multi-Version als Synchronisationspunkt: sobald ein Client online ist, Concurrency Control (MVCC) abgefangen: hier ge- synchronisiert er den Inhalt seiner lokalen Datenbank winnt einem ausgeklügelten Algorithmus folgend repro- mit der zentralen Instanz (Abb. 2). Dass es dabei nicht duzierbar immer eine bestimmte Dokumentenrevision, zu Konflikten kommt, ist der Tatsache zu verdanken, die verlierenden Revisionen werden ebenfalls abgespei- dass CouchDB UUIDs als Schlüssel für jeden Datensatz chert und können vom Client bei Bedarf zur Auflösung verwendet – die Gefahr einer Schlüsselkollision auch des inhaltlichen Konflikts herangezogen werden. Durch über mehrere Rechner hinweg ist extrem gering. Bei der dieses Verfahren kann auf Locking verzichtet werden, Synchronisation ist zu beachten, dass nicht alle in der was sich hinsichtlich der Performance positiv auswirkt. zentralen Datenbank gespeicherten GPS-Koordinaten Durch den Einsatz des Replikationsverfahrens eignet mit den mobilen Clients synchronisiert werden dürfen. sich CouchDB sehr gut für die Implementierung von An- Einerseits wären das bereits nach kurzer Zeit zu viele wendungen, die offlinefähig sein müssen: Während der Datensätze für den auf dem Smartphone limitierten Offlinephase werden die Daten in einer lokalen Couch- Speicher. Andererseits darf jeder Benutzer natürlich nur DB gespeichert und anschließend mittels Replizierung seine eigenen Daten sehen. Die Synchronisation vom mit der zentralen Instanz synchronisiert – ein Vorgehen, Server zu den mobilen Clients muss also durch einen das wir in diesem Artikel demonstrieren werden. Filter abgesichert werden. Erfreulicherweise muss die Ein beliebtes Beispiel für die Verwendung von Couch- Synchronisation der Daten nicht selbst entwickelt wer- DB ist GroceryList, eine Einkaufsliste, die von mehreren den: CouchDB verfügt über einen sehr einfachen, aber Personen gleichzeitig eingesehen und bearbeitet werden dennoch leistungsfähigen Replikationsmechanismus, kann. Obwohl mit diesem Beispiel die meisten Funk- den wir für unsere Zwecke einsetzen können. Auf diese tionsprinzipien von CouchDB sehr gut erklärt werden Weise wird unsere App übrigens offlinefähig, ohne dass können, werden die Fähigkeiten von CouchDB, große wir viel Mühe investieren müssen. Datenmengen effizient zu verwalten, nur unzureichend dargestellt – wer hat schon Einkaufslisten mit mehreren CouchDB auf iOS tausend Einträgen? Unter [2] steht ein CouchDB Build zur Verfügung, der Aus diesem Grund soll im vorliegenden Artikel eine für die iOS-Plattform optimiert wurde. Sie werden Lauf-App entwickelt werden. Die von jedem Läufer bei sich jetzt vielleicht ein wenig wundern, aber wir wer- Verwendung der App aufgezeichneten GPS-Koordina- den diesen Build nicht verwenden. Stattdessen kommt ten sollen sowohl in einer lokalen CouchDB als auch in TouchDB zum Einsatz. Ein paar Worte der Erklärung einer zentralen Instanz in der Cloud abgelegt werden. scheinen angebracht. Wie bereits angesprochen, ba- Schon bei einem 5-km-Lauf werden zirka 300 bis 400 siert CouchDB auf Erlang. CouchDB für iOS besteht Wegpunkte aufgezeichnet, sodass schon bei einer recht im Prinzip nur aus einem sehr dünnen Wrapper um die überschaubaren Anzahl von Benutzern mit einem nicht in Erlang geschriebene CouchDB Engine. Das hört sichMobile Technology 3 | 2012 www.mobile360.de
  • 3. CouchDB mobile | iOS 039Abb. 1: Browserbasiertes Administrations-Frontend Futonzuerst einmal nach einer guten Idee an, hat aber zwei entscheidende Nachteile:die Erlang Runtime ist alleine schon zirka 4 bis 6 Megabyte groß und auch dieStartup-Zeit liegt mit 5 bis 10 Sekunden außerhalb dessen, was auf einem mo-bilen Device toleriert werden kann. Aus diesem Grund hat das CouchDB-Teamvor einiger Zeit damit begonnen, eine CouchDB-kompatible Engine speziell für Anzeigemobile Devices zu schreiben. Das Ergebnis ist TouchDB [3], [4]. Die Designzielefür TouchDB sind folgende: geringe Codegröße (idealerweise < 256 KB) schnelle Startzeiten (idealerweise < 100 ms) geringer Hauptspeicherverbrauch ausreichende Performance auf mobilen CPUsNeben diesen Zielen sind zwei weitere Treiber maßgeblich für die Architekturvon TouchDB verantwortlich: einerseits sind dies die Einschränkungen der iOS-Plattform (z. B. können keine Third-Party-Prozesse im Hintergrund ausgeführtwerden, was eine Architektur mit einem auf dem Smartphone ausgeführten lo-kalen Server ad absurdum führt), anderseits sollte das TouchDB API dem REST-basierten CouchDB API nachempfunden werden, um eine möglichst reibungsloseWeiterverwendung von existierenden Clients zu ermöglichen. Um den REST-basierten Zugriff zu ermöglichen, implementiert TouchDB ein NSURLProtocolund registriert das URL-Schema touchdb. Dank dieser Implementierung kann einiOS-Client ganz normal über REST-Aufrufe mit TouchDB kommunizieren. Werlieber Cocoa-Idiome nutzt, sollte CouchCocoa [5] verwenden: eine ursprünglichfür CouchDB geschriebene Zugriffsbibliothek, die aber auch für TouchDB ver-wendet werden kann.CouchCocoaCocoaTouchDB speichert seine Daten lokal in einer SQLite-Datenbank ab.MapReduce-Funktionen werden bei TouchDB nicht in JavaScript geschrieben,sondern in Objective-C nativ implementiert, was wir später noch detailliert be-trachten werden.Setup des ProjektsUm TouchDB und CouchCocoa in einem Projekt verwenden zu können, sindfolgende Schritte auszuführen:www.mobile360.de
  • 4. 040 iOS | CouchDB mobile Neues leeres Projekt anlegen (mit ARC) Für das Target CouchTo5K unter Build Phases die Im Projektverzeichnis ein Verzeichnis Vendor für die folgenden Frameworks aufnehmen: 3rd-Party-Bibliotheken anlegen - libz.dylib TouchDB-iOS klonen: - libsqlite3.dylib - git clone git://github.com/couchbaselabs/TouchDB- - SystemConfiguration.framework iOS.git - Security.framework Submodule aktualisieren: - CFNetwork.framework - cd TouchDB-iOS - Wieder zu Vendor wechseln - git submodule init CouchCocoa klonen: - git submodule update - git clone https://github.com/couchbaselabs/Couch- TouchDB.xcodeproj öffnen und das Target iOS Cocoa.git Framework bauen - cd CouchCocoa - Vendor/TouchDB-iOS/build/Products/Debug-ios- - git submodule init universal/TouchDB.frameworkper per Drag and - git submodule update Drop auf die Frameworksgruppe des CouchTo5K- - CouchCocoa.xcodeproj öffnen und das Target Projekts fallen lassen „iOS Framework“ bauen Für das Target CouchTo5K unter Build Settings - Vendor/CouchCocoa/build/Products/Debug-ios- nun Other Linker Flags auf -ObjC setzen universal/CouchCocoa.framework per Drag and Drop auf die Frameworkgruppe des CouchTo5K- Projekts fallen lassen Listing 1 (CouchDatabase *)connect UI für das Tracken von GPS Waypoints bauen { Wie man unter iOS die aktuelle Position bestimmt und CouchTouchDBServer *server = [CouchTouchDBServer sharedInstance]; Positionsveränderungen abonniert, ist bereits in anderen NSAssert(!server.error, @"Error initializing TouchDB server: %@", server.error); Artikeln detailliert beschrieben worden. Dennoch stellt self.database = [server databaseNamed:@"couch25k"]; die Bestimmung der aktuellen Position die Grundlage NSError *error; für unsere Lauf-App dar, daher soll hier wenigstens die if (! [self.database ensureCreated:&error]) { prinzipielle Herangehensweise kurz beschrieben werden. // raise error Um einen GPS-Track aufzuzeichnen, verwenden wir die self.connected = false; Klasse CLLocationManager aus dem CoreLocation- } Framework. Wir registrieren uns als CLLocationMana- self.connected = true; gerDelegate, um über Positionsänderungen informiert self.database.tracksChanges = YES; zu werden. Da die Ermittlung von GPS-Koordinaten return self.database; recht viel Energie verbraucht, sollte die Ermittlung von } GPS-Koordinaten erst dann aktiviert werden, wenn sie tatsächlich benötigt wird. Durch einen Aufruf der Me- thode startUpdatingLocation wird die Ermittlung der aktuellen Position gestartet. Der Location-Manager ruft Listing 2 nun bei jeder Positionsänderung die Delegate-Methode NSDictionary *trackpointProperties = [NSDictionary locationManager:didUpdateToLocation:fromLocation: dictionaryWithObjectsAndKeys: auf und liefert dabei sowohl die alte als auch die neue self.runnerName, @"user", Position. self.runName, @"run", [NSString stringWithFormat:@"%@", Daten lokal speichern [NSNumber numberWithDouble:newLocation.coordinate.latitude]], @"lat", Die so ermittelten GPS-Positionen speichern wir zu- [NSString stringWithFormat:@"%@", nächst in einer lokalen CouchDB. An dieser Stelle sei [NSNumber numberWithDouble:newLocation.coordinate.longitude]], @"lon", noch einmal darauf hingewiesen, dass CouchDB eine [NSString stringWithFormat:@"%@", newLocation.timestamp] , @"time", schemalose Datenbank ist – wir können also beliebig nil]; strukturierte Daten speichern. Zunächst müssen wir eine Verbindung zur Datenbank aufbauen (Listing 1). CouchDocument *trackpointDocument =[database untitledDocument]; Die einzelnen Trackpoints speichern wir als individu- RESTOperation* op = [trackpointDocument putProperties:trackpointProperties]; elle CouchDB-Dokumente ab. Jedes Dokument enthält [op onCompletion: ^{ natürlich Länge und Breite der aktuellen GPS-Position if (op.error) sowie den Zeitpunkt, an dem diese Information auf- NSLog(@"Couldnt save the new item"); gezeichnet wurde. Darüber hinaus speichern wir auch }]; noch den Namen des Benutzers sowie die ID des aktu- [op start]; ellen Laufs mit jedem Dokument ab. Diese Information benötigen wir später, um per Map Reduce verschiede-Mobile Technology 3 | 2012 www.mobile360.de
  • 5. CouchDB mobile | iOS 041ne Auswertungen über die gesamte Menge der in derDatenbank abgelegten Daten auszuführen, zum Beispiel In einer schemalosen Daten-um die Trackpoints eines ganz bestimmten Laufs zu er-mitteln oder die Liste aller Läufe eines einzelnen Läufers. bank lassen sich beliebig struk- Um ein neues CouchDB-Dokument anzulegen, ver-wenden wir das CouchCocoa API. Die Methode untit- turierte Daten speichern.ledDocument der Klasse CouchDatabase erzeugt einleeres Dokument. Dieses Dokument können wir nunmittels der Methode putProperties mit Informationen zum Einsatz [1]. Bei diesem zweistufigen Vorgehenbefüllen. Da CouchDB schemalos arbeitet, bietet es sich werden zunächst alle Daten der Datenbank mithilfean, die Daten ganz zwanglos mittels eines NSDictionary einer Map-Funktion entsprechend den Anforderun-zu übergeben. gen der gewünschten Abfrage gruppiert. Das Ergeb- Noch sind die Daten nicht in der Datenbank gespei- nis der Map-Funktion ist eine Liste von Tupeln. Wiechert. Der Aufruf der Methode putProperties liefert als diese Tupel aufgebaut sind, hängt stark von der ent-Ergebnis eine RESTOperation, die ausgeführt werden sprechenden Abfrage ab. Meistens werden jedoch einmuss, um die Daten in der Datenbank zu speichern. oder mehrere Attribute der verarbeiteten DokumenteDas erfolgt dann durch den Aufruf der Methode start als Element der Tupel verwendet. Jede Map-Funktionauf der Operation. Eventuelle Probleme beim Speichern verarbeitet im Prinzip alle Dokumente einer Daten-können mittels eines Completion Handlers behandelt bank, allerdings liefert sie nicht für jedes Dokumentwerden (Listing 2). Mit diesem relativ einfachen Vorge- ein Ergebnis. Bedenken Sie, dass eine Datenbank be-hen werden also alle Trackpoints des aktuellen Laufs in dingt durch die schemalose Natur von CouchDB be-der lokalen CouchDB auf dem Smartphone des Läufers liebige Dokumententypen enthalten kann. Also mussgespeichert. Am Ende des Laufs wird die Aufzeichnung jede Map-Funktion überprüfen, ob das aktuelle Doku-der GSP-Position mittels [locationManager stopUp- ment verarbeitet werden soll oder nicht. Möchte mandatingLocation] beendet. innerhalb eines Dokumententyps nur einen bestimm- ten Bereich der Daten analysieren, kann die AbfrageDarstellung der Karte durch die Angabe einer oberen und unteren SchrankeNach dem Lauf möchte man sich vielleicht die zurück- eingegrenzt werden. Diese Schranken sind wiederumgelegte Strecke auf einer Karte anschauen. Hierzu müs- Tupel, die genauso aufgebaut sein müssen wie die vonsen wir alle GPS-Koordinaten des entsprechenden Laufs der jeweiligen Map-Funktion produzierten Tupel. Hataus der lokalen Datenbank extrahieren und sie dann an- man die gewünschten Daten auf diese Weise aufberei-schließend in der richtigen Reihenfolge als Punkte eines tet und gefiltert, kann mithilfe einer Reduce-FunktionPolygons auf einer Karte darstellen. in einem abschließenden Schritt eine Auswertung über Datenbankfragen werden in CouchDB nicht mit diese Daten ausgeführt werden. Zum Beispiel könnteSQL formuliert, stattdessen kommt Map Reduce man die Anzahl der zurückgelieferten Datensätze zäh- !"# $%&" ["run-peterfriese–5","2012–03–27 06:40:33 +0000"] { "_id":"AB5FA179-D305–42D5-B863–0F50F32E54D5", "_rev":"1–81108A3D–051D–4139-B4C5–9F031185870E", "run":"run-peterfriese–5", "user":"peterfriese", "lon":"11.59023645278115", "time":"2012–03–27 06:40:33 +0000", "lat":"48.13086178722513" } ["run-peterfriese–5","2012–03–27 06:40:51 +0000"] { "_id":"AB5FA179-D305–42D5-B863–0F50F32E54D5", "_rev":"1–81108A3D–051D–4139-B4C5–9F031185870E", "run":"run-peterfriese–5", "user":"peterfriese", "lon":"11.59022621909413", "time":"2012–03–27 06:40:51 +0000", "lat":"48.13059268764777" }Tabelle 1: Ergebnisse der Map-Funktionwww.mobile360.de 3 | 2012 Mobile Technology
  • 6. 042 iOS | CouchDB mobile len oder die der Werte in einem bestimmten Attribut Listing 3 bilden. Die Map-Funktion muss seiteneffektfrei sein und bei CouchDesignDocument *designViewWaypointsByRun = [self.database jedem Aufruf für die gleichen Eingabeparameter stets designDocumentWithName: @"couch25k"]; die gleichen Ergebnisse liefern. Auch die Reduce-Funk- [designViewWaypointsByRun defineViewNamed: @"waypoints_by_run" mapBlock: tion muss seiteneffektfrei ausgeführt werden und darf MAPBLOCK({ nur von den Eingabeparametern abhängig sein. Diese NSString *run = (NSString *)[doc objectForKey:@"run"]; Eigenschaften erlauben eine parallele und von einan- id time = [doc objectForKey:@"time"]; der unabhängige Ausführung der beiden Funktionen NSMutableArray *key = [[NSMutableArray alloc] init]; – Grundlage zum Beispiel für eine Verteilung der Be- [key addObject:run]; rechnung auf mehrere Rechner. Außerdem wird so eine [key addObject:time]; inkrementelle Verarbeitung der Datensätze ermöglicht: emit(key, doc); Die Map-Funktion wird bereits beim Einfügen eines }) version: @"1.1"]; neuen Datensatzes ausgeführt, sodass Abfragen später über die so ermittelten Schlüssel ausgeführt werden kön- nen – mit entsprechenden Geschwindigkeitsvorteilen. Listing 4 Für die Ermittlung der Trackpoints eines Laufs reicht es aus, eine Map-Funktion in Kombination mit einer obe- RESTOperation *fetch = [query start]; ren und unteren Schranke zu verwenden. [fetch onCompletion:^{ In CouchDB werden Map/Reduce-Funktionen üb- CLLocation *previous = nil; licherweise in JavaScript geschrieben. In TouchDB für CLLocationDistance distance = 0; iOS ist das jedoch derzeit nicht möglich; die Integration for (CouchQueryRow *row in query.rows) { eines JavaScript-Interpreters würde den Designzielen id waypoint = row.value; von TouchDB widersprechen. Daher müssen wir die ge- CLLocationDegrees lat = [[waypoint objectForKey:@"lat"] doubleValue]; schilderte Map-Funktion in Objective-C programmie- CLLocationDegrees lon = [[waypoint objectForKey:@"lon"] doubleValue]; ren (Listing 3). CLLocation *location = [[CLLocation alloc] initWithLatitude:lat longitude:lon]; Zunächst wird ein so genanntes Designdokument [self addNewLocation:location]; angelegt. Wie bereits mehrfach erwähnt, ist CouchDB if (previous) { schemalos – nicht nur Daten werden als Dokumente in distance += [location distanceFromLocation:previous]; der Datenbank gespeichert, sondern auch die Abfragen. } Ein Designdokument kann eine beliebige Anzahl von previous = location; Map/Reduce-Funktionen aufnehmen. Normalerweise } werden Designdokumente in der Datenbank gespei- }]; chert, im Fall von TouchDB ist das anders: hier werden Designdokumente nicht persistiert, sondern müssen bei jedem Start der Anwendung neu angelegt werden. In Listing 5 dem soeben angelegten Designdokument wird anschlie- ßend eine Map-Funktion mit dem Namen waypoints_ - (CouchLiveQuery *)queryRuns by_run definiert. Die Map-Funktion wird durch einen { Block implementiert. Die Signatur für MAPBLOCK // Create a view containing list items sorted by date: sieht wie folgt aus: CouchDesignDocument* design = [self.database designDocumentWithName: @"couch25k"]; #define MAPBLOCK(BLOCK) ^(NSDictionary* doc, void (^emit)(id key, id [design defineViewNamed: @"runs" mapBlock: MAPBLOCK({ value)){BLOCK} id run = [doc objectForKey:@"run"]; if (run) emit(run, nil); Der Block bekommt bei Ausführung also ein NSDic- }) tionary mit den Werten des aktuellen Dokuments reduceBlock:REDUCEBLOCK({ sowie einen Zeiger auf eine emit-Funktion geliefert. return [NSNumber numberWithInt:values.count]; Innerhalb des Blocks bauen wir nun ein Tupel der }) Form (Name-des-Laufs, Zeitstempel) auf. Dieses version: @"1.0"]; Tupel wird nun gemeinsam mit dem ursprünglichen CouchLiveQuery* query = [[[self.database designDocumentWithName: @"couch25k"] Dokument an die emit-Funktion gesendet. Das Resul- queryViewNamed: @"runs"] asLiveQuery]; tat ist eine View, die alle Dokumente über Schlüssel [query setGroupLevel:1]; der Form (Names-des-Laufs, Zeitstempel) erreichbar query.descending = YES; macht. Tabelle 1 zeigt einen Ausschnitt aus der durch return query; die Map-Funktion aufgebauten Struktur. Wie man se- } hen kann, sind die Schlüssel Tupel, die aus dem Namen des Laufs sowie dem Zeitstempel aufgebaut sind. AlsMobile Technology 3 | 2012 www.mobile360.de
  • 7. CouchDB mobile | iOS 043Wert wird das jeweilige Dokument (d. h. der GPS Trackpoint) zurückgegeben. Der Inhalt der View wird von der TouchDB Runtime in entsprechenden Tabel-len in der zugrunde liegenden SQLite-Datenbank gespeichert, um später effizientdarauf zurückgreifen zu können. Die Map-Funktion wird für jeden Datensatz nurein einziges Mal aufgerufen, egal wie oft die entsprechende View abgefragt wird.Ändert sich später einmal ein Datensatz oder wird ein neuer Datensatz hinzuge-fügt, wird die Map-Funktion genau für diesen einen Datensatz erneut aufgerufen.So wird auf eine recht effiziente Weise sichergestellt, dass immer ein aktuellerIndex vorliegt. Um alle Trackpoints eines bestimmten Laufs zu ermitteln, müssen wir Start-und Endschlüssel der Abfrage entsprechend setzen. Angenommen, der Lauf hatden Namen hamburg-17, so lautet der Startschlüssel folglich (hamburg-17). AlsEndschlüssel geben wir das Tupel (hamburg-17, ufff0) an, wobei es sich bei ufff0um ein sehr hohes Unicode-Zeichen handelt, was mit ziemlicher Wahrscheinlich-keit niemals Teil eines Schlüssels sein wird. So stellen wir sicher, dass die Abfragealle Dokumente zurückliefert, deren Schlüssel den Namen des gewünschten Laufsenthält. Alle anderen Dokumente werden von der Abfrage ignoriert: CouchQuery *query = [[self.database designDocumentWithName: @"couch25k"] queryViewNamed: @"waypoints_ by_run"]; [query setStartKey:[NSArray arrayWithObjects:self.runKey, nil]]; [query setEndKey:[NSArray arrayWithObjects:self.runKey, @"ufff0", nil]];Nun endlich kann die Abfrage ausgeführt werden. Das Ergebnis der Abfra-ge ist eine Liste von CouchQueryRows, die jeweils aus dem Schlüssel in derForm (Name-des-Laufs, Zeitstempel) und dem jeweiligen Dokument bestehen.CouchDB sortiert die Ergebnisse einer Abfrage stets anhand der Schlüssel und Anzeigefolgt dabei der unter [6] beschriebenen Sortierreihenfolge. Durch das Anhän-gen des Zeitstempels an die Schlüssel erreichen wir, dass die von der Abfragezurückgelieferten Ergebnisse stets chronologisch aufsteigend sortiert sind. DieWegpunkte müssen unbedingt in der chronologisch korrekten Abfolge geliefertwerden, da ansonsten die Abbildung auf die Landkarte nicht der tatsächlichenLaufstrecke entspräche. Im schlimmsten Fall würden wir nur ein Zickzack-Muster erhalten. Die Wegpunkte können nun sequenziell als Eckpunkte eines Polygons auf dieLandkarte übertragen werden, wobei gleichzeitig die zurückgelegte Distanz be-rechnet werden kann (Listing 4).Daten auf dem Client abfragenUm dem Benutzer eine Übersicht über sein Trainingspensum zu geben, benö-tigen wir noch eine Liste, in der alle bereits lokal abgespeicherten Läufe desBenutzers angezeigt werden. Zunächst benötigen wir dazu eine Map/ReduceView, die eine Liste aller Läufe des Benutzers aus der lokalen Datenbank zu-rückgibt (Listing 5). Auch hier wird zunächst ein Designdokument für die Map- und Reduce-Funk-tionen angelegt. Die durch die Map/Reduce-Funktionen definierte View erhältden Namen runs. In der Map-Funktion wird als Schlüssel der Wert des Attributsrun ausgegeben – das ist der symbolische Name des jeweiligen Laufs (in einerspäteren Ausbaustufe sollte die App beziehungsweise die Datenbank sicherstel-len, dass keine doppelten Namen vergeben werden, in der aktuellen Fassung derAnwendung ist der Benutzer hierfür selbst zuständig). Als Wert wird nil aus-gegeben – wir interessieren uns ja nur für den Namen des Laufs, die anderenAttribute sind uns egal. Das Ergebnis der Map-Funktion ist eine Liste der Läufe;allerdings taucht der Name jedes Laufs gleich mehrmals auf, und zwar so oft,wie es Trackpoints für diesen Lauf gibt. Das ist natürlich nicht das, was wirwollen, aber wir können die Situation ausnutzen: Einerseits können wir mithilfeder Reduce-Funktion die Anzahl der Trackpoints eines Laufs ermittlen. Ande-rerseits können wir das Ergebnis gruppieren, um so die Duplikate zu entfernen.www.mobile360.de
  • 8. 044 iOS | CouchDB mobile die Reduce-Funktion dazu eine Liste von Schlüssel und eine Liste von Werten. Wie viele Schlüssel beziehungsweise Werte die Reduce-Funktion bei jedem Aufruf er- hält, hängt vom Aufbau und Füllzustand des B-Baums ab, in dem das Ergebnis der Map-Funktion abgelegt ist. Detaillierte Informationen gibt es im (übrigens sehr empfehlenswerten) Buch „The Definiti- ve Guide To CouchDB“, das es sowohl als käuflich zu erwerbende Papierversion [7] gibt als auch als kostenlos verfügbare Onlineversion [8]. Reduce und Re-Redu- ce werden zum Beispiel unter [9] erklärt. Um die Anzahl der Trackpoints eines Laufs zu ermitteln, zählen wir einfach die Anzahl der Elemente im Parameter keys der Reduce-Funktion (wir könnten auch die Anzahl der Elemente im Parameter values zählen). Die mehrfach auftreten- den Ergebnisse können wir eliminieren,Abb. 2: Das browserbasierte Admin-Tool Futon indem wir das Ergebnis gruppieren. Da- bei aggregiert CouchDB die Ergebnisse Doch eins nach dem anderen: Zählen wir zunächst die der Reduce-Funktion für jeden eindeutigen Schlüssel in Trackpoints. Dazu fügen wir der View runs eine Redu- der Ergebnismenge. ce-Funktion hinzu. Reduce-Funktionen werden auf der Die so ermittelten Daten sollen nun in einer Liste dar- nach dem Schlüssel sortierten Ergebnismenge der je- gestellt werden. CouchCocoa unterstützt die Anzeige weiligen Map-Funktion ausgeführt. Als Eingabe erhält von Daten, die an ein Live-Query angebunden sind, mit der Klasse CouchUITableSource, mit dessen Hilfe ein UITableView schnell umgesetzt ist. Die relevanten Frag- Listing 6 mente des Quellcodes finden Sie in Listing 6. (void)viewDidLoad Daten zum Server synchronisieren { Bis jetzt haben wir uns hauptsächlich um den Teil der [super viewDidLoad]; App gekümmert, der auf dem Smartphone läuft. Im self.navigationItem.title = @"Run Log"; nächsten Schritt sollen die aufgezeichneten Daten in eine [self setupView]; zentrale Datenbank in der Cloud synchronisiert werden. } Eine zentrale Speicherung der Daten ermöglicht eine ganze Reihe von interessanten Diensten: Zum Beispiel (void)setupView könnte man dem Anwender grafische Auswertungen { seiner sportlichen Leistungen anzeigen, ähnlich wie das self.dataSource = [[CouchUITableSource alloc] init]; bei Runkeeper oder Nike+ möglich ist. Ein anderer inte- self.tableView.dataSource = self.dataSource; ressanter Einsatzzweck ist die Darstellung der aktuellen self.dataSource.tableView = self.tableView; Läuferpositionen in einem Rennen. Da CouchDB die self.dataSource.query = [[DatabaseAdapter sharedAdapter] queryRuns]; Daten nahezu live zwischen dem Smartphone und der } zentralen Datenbank synchronisiert, kann so eine relativ präzise Darstellung der aktuellen Position eines Läufers (void)couchTableSource:(CouchUITableSource*)source ermittelt werden. willUseCell:(UITableViewCell*)cell Die Synchronisierung von Daten zwischen zwei Da- forRow:(CouchQueryRow*)row tenbankinstanzen ist vielleicht eines der wichtigsten { Features von CouchDB. Wer nun vermutet, dass die cell.backgroundColor = [UIColor whiteColor]; Synchronisierung von Daten zwischen zwei Datenbank- cell.selectionStyle = UITableViewCellSelectionStyleGray; instanzen aufwändig und kompliziert einzurichten ist, cell.textLabel.font = [UIFont fontWithName: @"Helvetica" size:18.0]; der irrt. Das Aufsetzen der Replikation zwischen zwei cell.textLabel.backgroundColor = [UIColor clearColor]; Datenbankinstanzen ist mit CouchDB so einfach, dass cell.textLabel.text = row.key; das entsprechende Kapitel im CouchDB Guide nur } schlappe vier Seiten umfasst [10]. In unserem Fall wollen wir eine asymmetrische Re-Mobile Technology 3 | 2012 www.mobile360.de
  • 9. CouchDB mobile | iOS 045plikation einrichten: Alle auf dem Smartphone aufge-zeichneten Daten sollen in die Cloud synchronisiert Listing 7werden, aber nicht alle in der Cloud gespeicherten {Daten sollen auch wieder auf das Smartphone syn- "_id": "_design/couch25k",chronisiert werden: Sobald mehrere Benutzer die App "_rev": "1-5542059a0466c96837142edc4802fe4a",verwenden, werden auch ihre Daten in der Cloud ge- "language": "javascript",speichert. Diese Daten sollen natürlich nicht auf die "filters": {Geräte der anderen Läufer synchronisiert werden. "by_user": "function(doc, rq) {Einerseits natürlich aus Gründen des Datenschutzes, if(doc.user == rq.query.username) { return true; }andererseits wäre die Datenmenge sicherlich bald zu return false;umfangreich für eine lokale Datenbank. Um nur benut- }"zerspezifische Daten zu synchroniseren, werden wir die }Synchronisierung auf dem Downstream mittels eines }Filters auf genau die dem jeweiligen Läufer zugeordne-ten Daten einschränken. Bevor wir aber überhaupt Daten synchronisieren kön-nen, benötigen wir eine CouchDB-Instanz. InstallierenSie dazu entweder eine lokale Instanz von CouchDB aufIhrem Entwicklungsrechner [11] oder melden Sie sichkostenlos bei Iris Couch [12] an. Anschließend benö- Listing 8tigen wir eine Datenbank, die wir entweder über dieKommandozeile mit curl -X PUT http://127.0.0.1:5984/ (void)startSynccouch25k oder über das browserbasierte Admin-Tool {Futon anlegen können. Futon erreichen Sie auf jeder if (self.connected) {CouchDB-Instanz unter http://localhost:5984/_utils/ NSURL *url = [NSURL URLWithString:@"http://peterfriese.iriscouch:5984/couch25k"];(Abb. 2). NSArray *replications = [self.database replicateWithURL:url exclusively: YES]; Clients können angeben, dass für die Synchronisa- CouchPersistentReplication *from = [replications objectAtIndex:0];tion mit dem entfernten System eine Filterfunktion from.continuous = YES;verwendet werden soll. Diese Filterfunktion muss auf from.filter = @"couch25k/by_user";dem entfernten System vorliegen, um eine effiziente Fil- NSDictionary *filterParams = [NSDictionary dictionaryWithObjectsAndKeys:terung zu ermöglichen – schließlich möchte man nicht @"peterfriese", @"username", nil];erst alle Daten übertragen und dann filtern, sondern from.query_params = filterParams;nur die wirklich benötigten Daten übertragen. Filter- }funktionen sind JavaScript-Funktionen, die genau wie }Map/Reduce-Funktionen in einem Designdokument ineiner CouchDB-Datenbank gespeichert werden. Lis-ting 7 zeigt einen Ausschnitt aus dem Designdokumentfür die couch25k-Datenbank, die wir für unser Beispiel Listing 9verwenden. Die Filter-Funktion by_user überprüft also, ob der in {"rows":[der Datenbankanfrage übergebene Benutzername mit {"key":"demo1","value":702},dem Feld user des aktuellen Dokuments übereinstimmt. {"key":"hellomtc17","value":351},Wenn der Filter aktiv ist, werden folglich nur Dokumen- {"key":"hellotame","value":645},te ausgeliefert, für die diese Bedingung erfüllt ist. {"key":"run-peterfriese-1","value":4}, Die Replikation wird clientseitig gestartet, indem {"key":"run-peterfriese-123","value":17},die Methode replicateWithURL auf der Klasse Couch- {"key":"run-peterfriese-19","value":182},Database aufgerufen wird. Als URL wird die Adresse {"key":"run-peterfriese-2","value":9},der Datenbankinstanz angegeben, mit der wir syn- {"key":"run-peterfriese-33","value":7},chronisieren wollen (also unsere eben gerade angelegte {"key":"run-peterfriese-34","value":7},Instanz auf dem lokalen Rechner oder auf Iriscouch. {"key":"run-peterfriese-35","value":29},com). Das Ergebnis dieses Aufrufs ist ein NSArray, {"key":"run-peterfriese-4","value":4},das zwei CouchPersistentReplication-Objekte enthält, {"key":"run-peterfriese-42","value":351},mit denen die Replikation vom entfernten System (Ele- {"key":"run-peterfriese-5","value":7},ment an Position 0) beziehungsweise zum entfernten {"key":"run-peterfriese-mtc1","value":53},System (Element an Position  1) konfiguriert werden {"key":"tame-tribute","value":1827},kann. Um den Downstream zu filtern, geben wir also {"key":"test1","value":4}den Namen der Filterfunktion in der Form Daten- ]}bankName/FilterName an. Außerdem übergeben wirwww.mobile360.de 3 | 2012 Mobile Technology
  • 10. 046 iOS | CouchDB mobile Für Systeme, die zur Erfassung von großen Daten gedacht sind, ist CouchDB durch seine Architektur eine gute Wahl. die Filterkriterien (hier den Benutzernamen) in einem gute Wahl. In vielen Fällen kommt man Dank des REST- Dictionary (Listing 8). Die Synchronisation läuft nun basierten Datenbank-APIs sogar ohne Application-Ser- im Hintergrund, solange die App aktiv ist, und sorgt ver aus. Wie kurz angedeutet wurde, kann CouchDB für einen zeitnahen Upload der lokal erfassten GPS- mithilfe von CouchApp sogar als Application-Server für Wegpunkte. Web-Apps dienen. In einem Folgeartikel werden wir auf den Einsatz von CouchDB auf Android eingehen und Daten auf der zentralen Datenbankinstanz abfragen das Thema CouchApps vertiefen. Die auf der zentralen Datenbankinstanz gespeicherten Daten können nun ganz regulär mit Map/Reduce Views abgefragt werden. Anders als unter TouchDB können Peter Friese arbeitet als Software Engineering Consultant für Zühl- CouchDB-Designdokumente in der Datenbank gespei- ke Engineering. Seine Schwerpunkte liegen auf der modellgetrie- benen Softwareentwicklung, der plattformübergreifenden chert werden. Designdokumente erhalten eine ID, die Entwicklung von mobilen Anwendungen (u. a. für iOS, Android, mit _design/ beginnt, um sie von anderen Dokumenten Windows Phone 7 und Mobile Web) sowie Eclipse als Plattform. besser unterscheiden zu können. Peter bloggt auf http://www.peterfriese.de und twittert unter @peterfriese. Um zum Beispiel eine Liste der Läufe zu ermitteln, benötigen wir die in Listing 8 dargestellte Map/Reduce View. Unter dem Namen byrun abgespeichert, kann sie dann zum Beispiel wie folgt aufgerufen werden: curl localhost:5984/couch25k/_design/couch25k/_view/by_ run?group=true und liefert das in Listing 9 dargestellte Ergebnis. Links & Literatur Dieses Ergebnis können wir beispielsweise mittels [1] Jeffrey Dean and Sanjay Ghemawat, MapReduce: Simplified Data eines geeigneten jQuery Scripts in einer Webseite als Processing on Large Clusters, http://research.google.com/archive/ Liste anzeigen. CouchDB unterstützt mittels so ge- mapreduce.html nannter CouchApps [13] die einfache Entwicklung [2] https://github.com/couchbase/iOS-Couchbase/ von Webapplikationen, die innerhalb von CouchDB [3] https://github.com/couchbaselabs/TouchDB-iOS ausgeführt werden und somit direkten Zugriff auf die Datenbank und die Map/Reduce Views haben. Eine Ein- [4] https://github.com/couchbaselabs/TouchDB-Android führung in die Welt der CouchApps würde den Rahmen [5] https://github.com/couchbaselabs/CouchCocoa des Artikels jedoch sprengen. [6] http://wiki.apache.org/couchdb/View_collation [7] J.Chris Anderson, Jan Lehnardt, Noah Slater, CouchDB: The Definitive Fazit Guide, O`Reilly Media 2010 In diesem Artikel haben Sie gesehen, wie CouchDB ein- [8] http://guide.couchdb.org/ gesetzt werden kann, um auf einfache Art und Weise [9] http://guide.couchdb.org/editions/1/en/views.html#reduce eine asymmetrische Synchronisierung zwischen den Da- [10] http://guide.couchdb.org/editions/1/en/replication.html tenbeständen einer mobilen Anwendung und einer zen- [11] http://wiki.apache.org/couchdb/Installation tralen Anwendung zu implementieren. Insbesondere für Systeme, die zur Erfassung von großen Datenmengen [12] http://www.iriscouch.com gedacht sind, ist CouchDB durch seine Architektur eine [13] http://couchapp.org/page/indexMobile Technology 3 | 2012 www.mobile360.de