презентацию

#couchbase #couchbaselite #ios
Мобильный NoSQL
и синхронизация
Никифоров Данил
•
Немного теории
•
Couchbase Lite / CRUD
•
Включение синхронизации
•
Demo / CRM
•
Q&A
Как мы обычно храним
данные в iOS?
‣
CoreData (ORM on top of SQLite,…)
‣
SQLite through FMDB
‣
NSUserDefaults (KV)
‣
iCloud / CloudKit (syncable)
‣
…hand-made something
‣
Couchbase Lite
SQLite
FMDB
Core Data
What is Couchbase Lite?
Couchbase Lite это легковесный Framework
реализующий документ-ориентированное и
синхронизируемое хранилище для мобильных
устройств и встраиваемых систем.
6.0+
2.3+
Легковесность:
•
Является библиотекой подключаемой к
приложению, а не дополнительным серверным
процессом.
•
Быстрый запуск на относительно медленных
CPU: <50ms на свежих iPhone.
•
Не высокое потребление памяти и хорошая
производительность.
Документо-ориентированность
•
Данные храниться ввиде JSON документов.
•
Отсутствует схема базы данных
•
Данные могут эволюционировать без необходимости явных миграций
•
Документы могут содержать сравнительно большие
вложения(attachments)
•
Map/reduce indexing позволяет искать объекты без необходимости
использовать особый язык запросов.
•
Документы могут содержать тексты и географические координаты
которые эффективно индексируются для full-text search или geoquerying
Синхронизируемость
•
Любые 2 копии базы могут быть синхронизированы
используя эффективный и надежный REST-based
протокол. (eventually consistent)
•
Решаются вопросы нестабильного соединения.
•
Конфликты синхронизации могут быть найдены и
разрешены основываясь на логике приложения
•
Поддержка on-demand и continuous
синхронизации
Storage: SQLite / ForestDB
•
ForestDB is a new storage engine (based on Hierarchical B+-Trees)
which can be used in CBL. It has some benefits in speed (2x-5x)
and RAM (lower) footprint.
•
ForestDB has also some drawbacks/issues:
•
Still in Beta: available in master branch (needs manual building
of ForestDB library).
•
No easy way to switch storage on the go
•
FullText search is very limited and slower than FTS3 (SQLite)
•
GEO Search is very limited (only point containment)
https://github.com/couchbase/couchbase-lite-ios/wiki/ForestDB
Быстрый Старт
•
Скачать и добавить Framework:
http://www.couchbase.com/mobile или из GitHub
•
#import <CouchbaseLite/CouchbaseLite.h>
•
можем уже записать документ в базу
Быстрый Старт
•
pod 'couchbase-lite-ios', ‘~> 1.0.3'
•
pod install
•
#import <CouchbaseLite/CouchbaseLite.h>
•
можем уже записать документ в базу
CRUD
CBLManager
* manager
= [[CBLManager alloc]init];
CBLDatabase * database = [manager databaseNamed:@"db-name"
error: &error];
NSDictionary * dictionary = @{@"first_name": @"Danil",
@"last_name" : @"Nikiforov",
@"timestamp" : @"2014-04-30T17:15"};
CBLDocument * document = [database createDocument];
CBLRevision * revision = [document putProperties: dictionary
error: &error];
{
NSString
docID = document.documentID;
"_id" :* "369A75CE-9C35-4B15-B127-51D2B96D1F5B",
"_rev": “1-54b1d651b3d0003c77807314a223baa5",
CBLDocument
*retrievedDoc = [database documentWithID: docID];
"first_name":"Danil",
"last_name" :"Nikiforov",
[retrievedDoc
"timestamp" deleteDocument:&error];
:”2014-04-30T17:15"
}
CRUD
CBLDocument *doc = [database documentWithID: docID];
[doc update:^BOOL(CBLUnsavedRevision * rev) {
/* can be called multiple times */
rev[@"status"] = status;
return YES;
} error:&err];
Revision History
•
При каждом изменении документа создается
новая ревизия (CBLRevision)
•
Удаление это создание ревизии с полем “_deleted”
•
Нам возможно потребуется устранять конфликты
•
[database compact];
Local Documents
_local/<your-id>
Models
@interface Person : CBLModel
@property
@property
@property
@property
(strong)
(strong)
(strong)
(strong)
NSString
NSString
NSDate
NSArray
@property (strong) Company
* firstName;
* lastName;
* timestamp;
* children;
* company;
-(UIImage*) photo;
@end
@implementation Person
@dynamic firstName, lastName, timestamp, company, children;
...
@end
CRUD
NSError * error = nil;
Person *
person = [[Person alloc] initWithNewDocumentInDatabase:database];
person.firstName = @"Andrew";
person.lastName = @"Tokarev";
{
person.timestamp
= [NSDate date];
“_id" : ="B6A59791-7B2D-4402-9F53-7CB146B2CE98",
person.children
@[@"Max"];
"_rev": ="1-f7c04cea9c877ea8692cb86a4a93cebe",
person.company
al_digit;
"firstName":"Andrew",
[person save:&error];
“lastName" :"Tokarev",
if (error)"timestamp":"2014-04-29T17:51:49.538Z",
{
/* to handle
errors properly */
"children":["Max"],
}
“company" :"DCD6F62A-B0FA-4D46-9874-03B9BF47D37F"
}
...
CBLDocument * existing_document = ...;
person = [Contact modelForDocument:existing_document];
[person deleteDocument:&error];
Attachments
- (void) setPhoto:(UIImage*)photo {
NSData * photoData = UIImagePNGRepresentation(photo);
[self setAttachmentNamed:@"photo"
withContentType:@"image/png"
content:photoData];
}
- (UIImage*)photo
{
NSData * photoData = [[self attachmentNamed:@"photo"] content];
}
return [UIImage imageWithData:photoData];
- (void) removePhoto
{
[self removeAttachmentNamed:@"photo"];
}
How to Search?
Map / Reduce
Map / Reduce
_id: apple-1
_id: apple-2
_id: orange-cat
Map / Reduce
_id: apple-1
_id: apple-2
_id: orange-cat
key
value
doc-id
Red Apple
nil
apple-1
Green Apple
nil
apple-2
Indexing
id
type
name
key
value
doc_id
apple-1
apple
Red Apple
Red Apple
nil
apple-1
Green
Apple
nil
apple-2
…
…
…
pa-1
Thai
pineapple
Pineapple
apple-2
apple
Green
Apple
orange-cat
orange
Kitty
xxx
…
…
View / Query
CBLView * view = [database viewNamed:@"fruits/Apples"];
[view setMapBlock:^(NSDictionary *doc, CBLMapEmitBlock emit) {
if ([doc[@“type"] isEqualToString:@"apple"]) {
emit (doc[@"name"], nil);
}
} version:@"1"];
CBLQuery * query = [view createQuery];
query.descending = YES;
query.limit = 10;
for (CBLQueryRow *row in [query run:&error]) {...}
All Documents Query
CBLQuery* query = [database createAllDocsQuery];
query.allDocsMode = kCBLOnlyConflicts;
CBLQueryEnumerator* result = [query run: &error];
for (CBLQueryRow* row in result) {
}
// do work
Persons for Company
-
(CBLQuery *)queryPersonsForCompany:(Company *)comapny
{
CBLView * view = [self.database viewNamed: @"personsForCompany"];
if(![view mapBlock]) {
[view setMapBlock:^(NSDictionary *doc, CBLMapEmitBlock emit){
NSString * companyId = doc[@"company"];
if (companyId) {
emit (companyId, nil);
}
}
} version:@"1"]; NSString * companyId = comapny.companyId;
CBLQuery * query = [view createQuery];
query.keys = @[companyId];
return query;
}
Synchronization
CBLReplication * push = [database createPushReplication:syncGatewayUrl];
CBLReplication * pull = [database createPullReplication:syncGatewayUrl];
for (CBLReplication * repl in @[push, pull]) {
}
[repl setContinuous:YES];
[repl start];
Authentication
CBLReplication * push = [database createPushReplication:syncGatewayUrl];
CBLReplication * pull = [database createPullReplication:syncGatewayUrl];
for (CBLReplication * repl in @[push, pull]) {
[repl setContinuous:YES];
[repl setAuthenticator:[self createFacebookAuthenticator]];
}
[repl start];
- (void) createFacebookAuthenticator {
return [CBLAuthenticator facebookAuthenticatorWithToken:@"access token"];
}
Sync Gateway
Sync Function
function(doc, oldDoc) {
/* we can route doc to channel(s) */
channel(“all_docs”);
/* we can create role, by assigning users to it */
role(userID, "role:admin"); /* we can require user/role/channel to proceed */
requireUser(userID);
requireRole(role);
requireAccess(channel);
/* throw some exception */
throw ({forbidden: "error message"})
/* we can grant user/role access to channel(s) */
access(userID,
“all_docs”);
access("role:admin", "all_docs");
}
/* we log something to console*/
log(“some text")
Channels
Edward
document_1
document_1
document_2
Channel A
document_2
document_3
Channel B
document_3
Jane
document_2
document_3
Replication Conflicts
1-abcd
1-abcd
2-abce
3-feed
2-abce
1-abcd
1-abcd
3-food
2-abce
3-feed
4-good
3-food
4-dead
2-abce
Doc: Native-API / Document / Conflicts
Demo
Couchbase CRM
‣
Документация:
‣
‣
http://docs.couchbase.com/couchbase-lite
Couchbase Mobile Google Group:
‣
https://groups.google.com/forum/#!forum/mobilecouchbase
#couchbase #couchbaselite #ios
Q&A
#couchbase #couchbaselite #ios
Thank you