SlideShare uma empresa Scribd logo
1 de 49
Baixar para ler offline
RETHINKING
SYNCING
AltConf 2019
What is Syncing?
Syncing
Syncing
Syncing
Syncing Variables
• Amount of Data
• When does user want to see or use data
• How much / how often does data change
• Offline access needed
Syncing with REST APIs
REST API
https://sync.server.com/green/GET
GET
PUT
POST
DELETE
https://sync.server.com/green/123
https://sync.server.com/green/456
https://sync.server.com/green/456
https://sync.server.com/green/789
To-Device Syncing Approaches
• The Everything Approach
• The Net Change Approach
• The Kinda Sorta Not Really Net Change Approach
• The As Needed Approach
• and more…
The Net Change Approach
https://sync.server.com/allmystuff?since=20180211
The Net Change Approach
• Updates data from known point in time
• More efficient implementation, more complex logic required
• Uses less network bandwidth
• Requires handling of adds, changes, deletes on server and mobile
• Requires one source of truth for “since” parameter
https://sync.server.com/allmystuff?since=20180211
The Net Change Approach
• Process:
• Make API calls - receive lists of “red,” “green,” and “blue” objects with actions identified
• Iterate each list - perform add / change / delete as needed to local storage
• Update UI
The Net Change Approach
• Prepare API Call:
static func urlRequest(for url:URL, parameters:[String:String]?) -> URLRequest {
var requestUrl = url
if let passedParameters = parameters {
var queryItems:[URLQueryItem] = []
for (parameterName, parameter) in passedParameters {
queryItems.append(URLQueryItem(name: parameterName, value: parameter))
}
var requestComponents = URLComponents(url: requestUrl, resolvingAgainstBaseURL: false)
if let currentQueryItems = requestComponents?.queryItems {
queryItems.append(contentsOf: currentQueryItems)
}
requestComponents?.queryItems = queryItems
if let queryRequestUrl = requestComponents?.url {
requestUrl = queryRequestUrl
}
}
let request = URLRequest(url: requestUrl)
return request
}
The Net Change Approach
• Make API Call:
static func fetchObjects<T:RemoteObject>(of type:T.Type,
completionHandler: @escaping (_ results:[T]) -> Void,
errorHandler: @escaping (_ error:Error) -> Void) -> Void {
let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL)
let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil),
completionHandler: { (data, response, error) in
if let actualError = error {
DispatchQueue.main.async {
errorHandler(actualError)
}
return
}
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
DispatchQueue.main.async {
errorHandler(NetworkingError.unexpectedHTTPStatusCode)
}
return
}
…
})
fetchTask?.resume()
}
The Net Change Approach
• Ingest Data:
do {
guard let payloadData = data,
let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any],
let resultsPayload = payload["results"] as? [[String:Any]] else {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
let results = T.ingest(json: resultsPayload)
DispatchQueue.main.async {
completionHandler(results)
}
} catch {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
The Net Change Approach
do {
guard let payloadData = data,
let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any],
let resultsPayload = payload["results"] as? [[String:Any]] else {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
let results = T.ingest(json: resultsPayload)
DispatchQueue.main.async {
completionHandler(results)
}
} catch {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
static func fetchObjects<T:RemoteObject>(of type:T.Type,
completionHandler: @escaping (_ results:[T]) -> Void,
errorHandler: @escaping (_ error:Error) -> Void) -> Void {
let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL)
let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil),
completionHandler: { (data, response, error) in
if let actualError = error {
DispatchQueue.main.async {
errorHandler(actualError)
}
return
}
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
DispatchQueue.main.async {
errorHandler(NetworkingError.unexpectedHTTPStatusCode)
}
return
}
…
})
fetchTask?.resume()
}
static func urlRequest(for url:URL, parameters:[String:String]?) -> URLRequest {
var requestUrl = url
if let passedParameters = parameters {
var queryItems:[URLQueryItem] = []
for (parameterName, parameter) in passedParameters {
queryItems.append(URLQueryItem(name: parameterName, value: parameter))
}
var requestComponents = URLComponents(url: requestUrl, resolvingAgainstBaseURL: false)
if let currentQueryItems = requestComponents?.queryItems {
queryItems.append(contentsOf: currentQueryItems)
}
requestComponents?.queryItems = queryItems
if let queryRequestUrl = requestComponents?.url {
requestUrl = queryRequestUrl
}
}
let request = URLRequest(url: requestUrl)
return request
}
do {
guard let payloadData = data,
let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any],
let resultsPayload = payload["results"] as? [[String:Any]] else {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
let results = T.ingest(json: resultsPayload)
DispatchQueue.main.async {
completionHandler(results)
}
} catch {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
static func fetchObjects<T:RemoteObject>(of type:T.Type,
completionHandler: @escaping (_ results:[T]) -> Void,
errorHandler: @escaping (_ error:Error) -> Void) -> Void {
let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL)
let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil),
completionHandler: { (data, response, error) in
if let actualError = error {
DispatchQueue.main.async {
errorHandler(actualError)
}
return
}
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
DispatchQueue.main.async {
errorHandler(NetworkingError.unexpectedHTTPStatusCode)
}
return
}
…
})
fetchTask?.resume()
}
do {
guard let payloadData = data,
let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any],
let resultsPayload = payload["results"] as? [[String:Any]] else {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
let results = T.ingest(json: resultsPayload)
DispatchQueue.main.async {
completionHandler(results)
}
} catch {
DispatchQueue.main.async {
errorHandler(NetworkingError.invalidJSONPayload)
}
return
}
static func fetchObjects<T:RemoteObject>(of type:T.Type,
completionHandler: @escaping (_ results:[T]) -> Void,
errorHandler: @escaping (_ error:Error) -> Void) -> Void {
let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL)
let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, param
completionHandler: { (data, response, error) in
if let actualError = error {
DispatchQueue.main.async {
errorHandler(actualError)
}
return
}
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
DispatchQueue.main.async {
errorHandler(NetworkingError.unexpectedHTTPStatusCode)
}
return
}
…
})
fetchTask?.resume()
}
From-Device Syncing Approaches
• The Online Only Approach
• The Persist-The-Network-Call Approach
• The Offline First Queue Approach
• and more…
Complications to REST API Syncing
• Offline mode
• Queueing uploads
• Dependencies
• Must fetch one entity before fetching another
• Must create one entity before creating another
• Model changes
• Sync API changes with releases
• Support older versions
REST API Syncing Summary
What if…
We didn’t
need to write
syncing
code…
any more?!
Eventually Consistent DB
• Server Database (can be NoSQL)
• Sync Server
• Mobile DB
Eventually Consistent DBs
• Examples:
• Couchbase
• Realm
• Google Firebase
• AWS AppSync
• IBM Cloudant
• others?
The idea:
Couchbase Server
Sync Server
Legacy DB
Services
Couchbase Lite
The idea:
Couchbase Server
Sync Server
Legacy DB
Services
Couchbase Lite
The idea:
Couchbase Server
Sync Server
Legacy DB
Services
Couchbase Lite
The idea:
Couchbase Server
Couchbase Lite
Sync Server
Legacy DB
Services
demo
What just happened:
Couchbase Server
Sync Server
Legacy DB
Services
Couchbase Lite
How do we implement?
• Model Objects
• Data Controller
• Fetching Data for Display
• Handling Refresh on Change
• Adding Data
• Deleting Data
Model Objects
struct BlueBar:Codable {
var userId: String
var blueIntDetail: Int
var type: String
var addedBy: String
}
struct RedBar:Codable {
var userId: String
var redIntDetail: Int
var type: String
var addedBy: String
}
struct GreenBar:Codable {
var userId: String
var greenIntDetail: Int
var type: String
var addedBy: String
}
Data Controller
init() {
do {
database = try Database(name: "rethinking")
} catch {
print("Unable to initialize Couchbase database 'rethinking'")
}
}
Data Controller (cont)
func setUpSyncing(at url:URL) {
guard let database = database else {
print("No Couchbase database, cannot start syncing")
return
}
let endpoint = URLEndpoint(url: url)
let config = ReplicatorConfiguration(database: database, target: endpoint)
config.replicatorType = .pushAndPull
config.continuous = true
config.authenticator = BasicAuthenticator(username: "demo_user", password: "password")
config.channels = ["demo_user"]
self.replicator = Replicator(config: config)
//Set up replication change listeners
//Database.setLogLevel(.verbose, domain: .replicator)
//Database.setLogLevel(.verbose, domain: .network)
self.replicator?.start()
}
Fetching Data for Display
func fetchGreenBars() throws -> [GreenBar] {
let documentType = "greenbars"
var fetchedGreenBars: [GreenBar] = []
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
let greenBarQuery = QueryBuilder.select(SelectResult.all())
.from(DataSource.database(database))
.where(Expression.property("type").equalTo(Expression.string(documentType)))
if greenBarQueryListenerToken == nil {
greenBarQueryListenerToken = greenBarQuery.addChangeListener({ [weak self] (change) in
self?.greenBarRefreshHandler?()
})
}
do {
for result in try greenBarQuery.execute() {
let resultDict = result.toDictionary()
if let dataDict = resultDict["rethinking"] {
let data = try JSONSerialization.data(withJSONObject: dataDict, options: .prettyPrinted)
let decodedObject = try self.jsonDecoder.decode(GreenBar.self, from: data)
fetchedGreenBars.append(decodedObject)
}
}
} catch {
print(error)
}
return fetchedGreenBars
}
Handling Refresh on Change
dataController?.greenBarRefreshHandler = { [weak self] in
do {
self?.greenBars = try self?.dataController?.fetchGreenBars() ?? []
} catch {
print("Encounted error attempting to fetch bar records")
}
self?.reloadSection(for: .green)
}
Adding Data
func add(greenBar: GreenBar) throws {
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
let doc = MutableDocument(id: greenBar.userId)
let data = try jsonEncoder.encode(greenBar)
guard let json = try JSONSerialization.jsonObject(with: data) as? [String:Any] else {
throw DataControllerModelError.cannotSerializeJSONError
}
doc.setData(json)
try database.saveDocument(doc)
print("Added green bar: (greenBar.greenIntDetail)")
}
Deleting Data
func delete(greenBar: GreenBar) throws {
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
guard let documentToDelete = database.document(withID: greenBar.userId) else {
throw DataControllerModelError.notFoundError
}
try database.deleteDocument(documentToDelete)
}
struct BlueBar:Codable {
var userId: String
var blueIntDetail: Int
var type: String
var addedBy: String
}
struct RedBar:Codable {
var userId: String
var redIntDetail: Int
var type: String
var addedBy: String
}
struct GreenBar:Codable {
var userId: String
var greenIntDetail: Int
var type: String
var addedBy: String
}
init() {
do {
database = try Database(name: "rethinking")
} catch {
print("Unable to initialize Couchbase database 'rethinking'")
}
}
func fetchGreenBars() throws -> [GreenBar] {
let documentType = "greenbars"
var fetchedGreenBars: [GreenBar] = []
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
let greenBarQuery = QueryBuilder.select(SelectResult.all())
.from(DataSource.database(database))
.where(Expression.property("type").equalTo(Expression.string(documentType)))
if greenBarQueryListenerToken == nil {
greenBarQueryListenerToken = greenBarQuery.addChangeListener({ [weak self] (change) in
self?.greenBarRefreshHandler?()
})
}
do {
for result in try greenBarQuery.execute() {
let resultDict = result.toDictionary()
if let dataDict = resultDict["rethinking"] {
let data = try JSONSerialization.data(withJSONObject: dataDict, options: .prettyPrinted)
let decodedObject = try self.jsonDecoder.decode(GreenBar.self, from: data)
fetchedGreenBars.append(decodedObject)
}
}
} catch {
print(error)
}
return fetchedGreenBars
}
dataController?.greenBarRefreshHandler = { [weak self] in
do {
self?.greenBars = try self?.dataController?.fetchGreenBars() ?? []
} catch {
print("Encounted error attempting to fetch bar records")
}
self?.reloadSection(for: .green)
}
func add(greenBar: GreenBar) throws {
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
let doc = MutableDocument(id: greenBar.userId)
let data = try jsonEncoder.encode(greenBar)
guard let json = try JSONSerialization.jsonObject(with: data) as? [String:Any] else {
throw DataControllerModelError.cannotSerializeJSONError
}
doc.setData(json)
try database.saveDocument(doc)
print("Added green bar: (greenBar.greenIntDetail)")
}
func delete(greenBar: GreenBar) throws {
guard let database = self.database else {
throw DataControllerModelError.noDatabaseError
}
guard let documentToDelete = database.document(withID: greenBar.userId) else {
throw DataControllerModelError.notFoundError
}
try database.deleteDocument(documentToDelete)
}
Eventually Consistent DB Advantages
• Significant simplification of syncing code
• Set up DB, set up syncing call. ~10-20 lines of code
• No API calls needed*
• Dependencies much easier to handle
• Can use nested objects
• Thread safety - can ingest model objects in background and
*safely* use them in main queue. Try doing that in Core Data!
* You might need to do authentication / login with an API call prior to setting up your sync
Eventually Consistent DB Advantages (con’t)
• Significant simplification of model & ingestion code
• No Core Data model needed
• No mogenerated ManagedObjectSubclasses
• Models can be classes or structs using Codable - very simple
to code
• Simplification of services
• Fewer, less complex endpoints
Eventually Consistent DB Disadvantages
• Lack of familiarity
• Example: client refreshed source DB for staging - crushed our
CB instance
• Architecture requires different thinking for common use cases
• Cannot control priority of syncing - “eventual consistency”
• No NSFetchedResultsController-type code (at least in
Couchbase Mobile)
Eventually Consistent DB Best Use Cases
• Handle slowly changing “master” data
• For example, organizing data or lists of choices
• User created data, especially offline
• * Not necessarily images…
• Not ideal for large amounts of quickly changing data
questions?
@jwkeeley
THANK YOU
martiancraft.com
AltConf 2019

Mais conteúdo relacionado

Mais procurados

Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch - Dynami...
Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch -  Dynami...Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch -  Dynami...
Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch - Dynami...Flink Forward
 
Engage 2013 - Why Upgrade to v10 Tag
Engage 2013 - Why Upgrade to v10 TagEngage 2013 - Why Upgrade to v10 Tag
Engage 2013 - Why Upgrade to v10 TagWebtrends
 
Elk with Openstack
Elk with OpenstackElk with Openstack
Elk with OpenstackArun prasath
 
Advanced .NET Data Access with Dapper
Advanced .NET Data Access with Dapper Advanced .NET Data Access with Dapper
Advanced .NET Data Access with Dapper David Paquette
 
Real-time data analysis using ELK
Real-time data analysis using ELKReal-time data analysis using ELK
Real-time data analysis using ELKJettro Coenradie
 
RMLL 2014 - LDAP Synchronization Connector
RMLL 2014 - LDAP Synchronization ConnectorRMLL 2014 - LDAP Synchronization Connector
RMLL 2014 - LDAP Synchronization ConnectorClément OUDOT
 
Parse cloud code
Parse cloud codeParse cloud code
Parse cloud code維佋 唐
 
Querying Data Pipeline with AWS Athena
Querying Data Pipeline with AWS AthenaQuerying Data Pipeline with AWS Athena
Querying Data Pipeline with AWS AthenaYaroslav Tkachenko
 
Data Analytics Service Company and Its Ruby Usage
Data Analytics Service Company and Its Ruby UsageData Analytics Service Company and Its Ruby Usage
Data Analytics Service Company and Its Ruby UsageSATOSHI TAGOMORI
 
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...confluent
 
Designing an API for the Internet of Things
Designing an API for the Internet of ThingsDesigning an API for the Internet of Things
Designing an API for the Internet of ThingsKevin Swiber
 
Intro to fog and openstack jp
Intro to fog and openstack jpIntro to fog and openstack jp
Intro to fog and openstack jpSatoshi Konno
 
Using OpenStack With Fog
Using OpenStack With FogUsing OpenStack With Fog
Using OpenStack With FogMike Hagedorn
 
Leveraging parse.com for Speedy Development
Leveraging parse.com for Speedy DevelopmentLeveraging parse.com for Speedy Development
Leveraging parse.com for Speedy DevelopmentAndrew Kozlik
 
Sharepoint Saturday India Online best practice for developing share point sol...
Sharepoint Saturday India Online best practice for developing share point sol...Sharepoint Saturday India Online best practice for developing share point sol...
Sharepoint Saturday India Online best practice for developing share point sol...Shakir Majeed Khan
 
Building a friendly .NET SDK to connect to Space
Building a friendly .NET SDK to connect to SpaceBuilding a friendly .NET SDK to connect to Space
Building a friendly .NET SDK to connect to SpaceMaarten Balliauw
 
StackMate - CloudFormation for CloudStack
StackMate - CloudFormation for CloudStackStackMate - CloudFormation for CloudStack
StackMate - CloudFormation for CloudStackChiradeep Vittal
 

Mais procurados (20)

Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch - Dynami...
Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch -  Dynami...Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch -  Dynami...
Flink Forward SF 2017: David Hardwick, Sean Hester & David Brelloch - Dynami...
 
Engage 2013 - Why Upgrade to v10 Tag
Engage 2013 - Why Upgrade to v10 TagEngage 2013 - Why Upgrade to v10 Tag
Engage 2013 - Why Upgrade to v10 Tag
 
Elk with Openstack
Elk with OpenstackElk with Openstack
Elk with Openstack
 
Node.js and Parse
Node.js and ParseNode.js and Parse
Node.js and Parse
 
Advanced .NET Data Access with Dapper
Advanced .NET Data Access with Dapper Advanced .NET Data Access with Dapper
Advanced .NET Data Access with Dapper
 
Ajax
AjaxAjax
Ajax
 
Xml http request
Xml http requestXml http request
Xml http request
 
Real-time data analysis using ELK
Real-time data analysis using ELKReal-time data analysis using ELK
Real-time data analysis using ELK
 
RMLL 2014 - LDAP Synchronization Connector
RMLL 2014 - LDAP Synchronization ConnectorRMLL 2014 - LDAP Synchronization Connector
RMLL 2014 - LDAP Synchronization Connector
 
Parse cloud code
Parse cloud codeParse cloud code
Parse cloud code
 
Querying Data Pipeline with AWS Athena
Querying Data Pipeline with AWS AthenaQuerying Data Pipeline with AWS Athena
Querying Data Pipeline with AWS Athena
 
Data Analytics Service Company and Its Ruby Usage
Data Analytics Service Company and Its Ruby UsageData Analytics Service Company and Its Ruby Usage
Data Analytics Service Company and Its Ruby Usage
 
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
Closing the Loop in Extended Reality with Kafka Streams and Machine Learning ...
 
Designing an API for the Internet of Things
Designing an API for the Internet of ThingsDesigning an API for the Internet of Things
Designing an API for the Internet of Things
 
Intro to fog and openstack jp
Intro to fog and openstack jpIntro to fog and openstack jp
Intro to fog and openstack jp
 
Using OpenStack With Fog
Using OpenStack With FogUsing OpenStack With Fog
Using OpenStack With Fog
 
Leveraging parse.com for Speedy Development
Leveraging parse.com for Speedy DevelopmentLeveraging parse.com for Speedy Development
Leveraging parse.com for Speedy Development
 
Sharepoint Saturday India Online best practice for developing share point sol...
Sharepoint Saturday India Online best practice for developing share point sol...Sharepoint Saturday India Online best practice for developing share point sol...
Sharepoint Saturday India Online best practice for developing share point sol...
 
Building a friendly .NET SDK to connect to Space
Building a friendly .NET SDK to connect to SpaceBuilding a friendly .NET SDK to connect to Space
Building a friendly .NET SDK to connect to Space
 
StackMate - CloudFormation for CloudStack
StackMate - CloudFormation for CloudStackStackMate - CloudFormation for CloudStack
StackMate - CloudFormation for CloudStack
 

Semelhante a Rethinking Syncing at AltConf 2019

Data models in Angular 1 & 2
Data models in Angular 1 & 2Data models in Angular 1 & 2
Data models in Angular 1 & 2Adam Klein
 
NoSQL meets Microservices - Michael Hackstein
NoSQL meets Microservices -  Michael HacksteinNoSQL meets Microservices -  Michael Hackstein
NoSQL meets Microservices - Michael Hacksteindistributed matters
 
Asynchronous JavaScript & XML (AJAX)
Asynchronous JavaScript & XML (AJAX)Asynchronous JavaScript & XML (AJAX)
Asynchronous JavaScript & XML (AJAX)Adnan Sohail
 
API Days Paris - Automatic Testing of (RESTful) API Documentation
API Days Paris - Automatic Testing of (RESTful) API DocumentationAPI Days Paris - Automatic Testing of (RESTful) API Documentation
API Days Paris - Automatic Testing of (RESTful) API DocumentationRouven Weßling
 
Protocol-Oriented Networking
Protocol-Oriented NetworkingProtocol-Oriented Networking
Protocol-Oriented NetworkingMostafa Amer
 
Ajax tutorial by bally chohan
Ajax tutorial by bally chohanAjax tutorial by bally chohan
Ajax tutorial by bally chohanWebVineet
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxJirat Kijlerdpornpailoj
 
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션AWS Step Functions을 활용한 서버리스 앱 오케스트레이션
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션Amazon Web Services Korea
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSam Brannen
 
Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Maarten Balliauw
 
Introduction to the SharePoint Client Object Model and REST API
Introduction to the SharePoint Client Object Model and REST APIIntroduction to the SharePoint Client Object Model and REST API
Introduction to the SharePoint Client Object Model and REST APIRob Windsor
 
Nordic APIs - Automatic Testing of (RESTful) API Documentation
Nordic APIs - Automatic Testing of (RESTful) API DocumentationNordic APIs - Automatic Testing of (RESTful) API Documentation
Nordic APIs - Automatic Testing of (RESTful) API DocumentationRouven Weßling
 
Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Claire Townend Gee
 
Web Technologies - forms and actions
Web Technologies -  forms and actionsWeb Technologies -  forms and actions
Web Technologies - forms and actionsAren Zomorodian
 
Http programming in play
Http programming in playHttp programming in play
Http programming in playKnoldus Inc.
 

Semelhante a Rethinking Syncing at AltConf 2019 (20)

Data models in Angular 1 & 2
Data models in Angular 1 & 2Data models in Angular 1 & 2
Data models in Angular 1 & 2
 
NoSQL meets Microservices - Michael Hackstein
NoSQL meets Microservices -  Michael HacksteinNoSQL meets Microservices -  Michael Hackstein
NoSQL meets Microservices - Michael Hackstein
 
Asynchronous JavaScript & XML (AJAX)
Asynchronous JavaScript & XML (AJAX)Asynchronous JavaScript & XML (AJAX)
Asynchronous JavaScript & XML (AJAX)
 
API Days Paris - Automatic Testing of (RESTful) API Documentation
API Days Paris - Automatic Testing of (RESTful) API DocumentationAPI Days Paris - Automatic Testing of (RESTful) API Documentation
API Days Paris - Automatic Testing of (RESTful) API Documentation
 
Reduxing like a pro
Reduxing like a proReduxing like a pro
Reduxing like a pro
 
Protocol-Oriented Networking
Protocol-Oriented NetworkingProtocol-Oriented Networking
Protocol-Oriented Networking
 
Ajax tutorial by bally chohan
Ajax tutorial by bally chohanAjax tutorial by bally chohan
Ajax tutorial by bally chohan
 
TPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and FluxTPSE Thailand 2015 - Rethinking Web with React and Flux
TPSE Thailand 2015 - Rethinking Web with React and Flux
 
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션AWS Step Functions을 활용한 서버리스 앱 오케스트레이션
AWS Step Functions을 활용한 서버리스 앱 오케스트레이션
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. REST
 
Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...
 
Introduction to the SharePoint Client Object Model and REST API
Introduction to the SharePoint Client Object Model and REST APIIntroduction to the SharePoint Client Object Model and REST API
Introduction to the SharePoint Client Object Model and REST API
 
Nordic APIs - Automatic Testing of (RESTful) API Documentation
Nordic APIs - Automatic Testing of (RESTful) API DocumentationNordic APIs - Automatic Testing of (RESTful) API Documentation
Nordic APIs - Automatic Testing of (RESTful) API Documentation
 
Ajax
AjaxAjax
Ajax
 
Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0Swift LA Meetup at eHarmony- What's New in Swift 2.0
Swift LA Meetup at eHarmony- What's New in Swift 2.0
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
Web Technologies - forms and actions
Web Technologies -  forms and actionsWeb Technologies -  forms and actions
Web Technologies - forms and actions
 
Http programming in play
Http programming in playHttp programming in play
Http programming in play
 
Ajax
AjaxAjax
Ajax
 
Android dev 3
Android dev 3Android dev 3
Android dev 3
 

Último

FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCRFULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCRnishacall1
 
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...Niamh verma
 
9892124323 | Book Call Girls in Juhu and escort services 24x7
9892124323 | Book Call Girls in Juhu and escort services 24x79892124323 | Book Call Girls in Juhu and escort services 24x7
9892124323 | Book Call Girls in Juhu and escort services 24x7Pooja Nehwal
 
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun service
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun serviceCALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun service
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun serviceanilsa9823
 
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual service
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual serviceCALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual service
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual serviceanilsa9823
 
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost LoverPowerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost LoverPsychicRuben LoveSpells
 
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort ServiceBDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort ServiceDelhi Call girls
 
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,Pooja Nehwal
 
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...wyqazy
 

Último (9)

FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCRFULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
 
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...
Chandigarh Call Girls Service ❤️🍑 9115573837 👄🫦Independent Escort Service Cha...
 
9892124323 | Book Call Girls in Juhu and escort services 24x7
9892124323 | Book Call Girls in Juhu and escort services 24x79892124323 | Book Call Girls in Juhu and escort services 24x7
9892124323 | Book Call Girls in Juhu and escort services 24x7
 
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun service
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun serviceCALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun service
CALL ON ➥8923113531 🔝Call Girls Gomti Nagar Lucknow best Night Fun service
 
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual service
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual serviceCALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual service
CALL ON ➥8923113531 🔝Call Girls Saharaganj Lucknow best sexual service
 
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost LoverPowerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
 
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort ServiceBDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
 
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,
Call US Pooja 9892124323 ✓Call Girls In Mira Road ( Mumbai ) secure service,
 
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...
哪里有卖的《俄亥俄大学学历证书+俄亥俄大学文凭证书+俄亥俄大学学位证书》Q微信741003700《俄亥俄大学学位证书复制》办理俄亥俄大学毕业证成绩单|购买...
 

Rethinking Syncing at AltConf 2019

  • 6. Syncing Variables • Amount of Data • When does user want to see or use data • How much / how often does data change • Offline access needed
  • 9. To-Device Syncing Approaches • The Everything Approach • The Net Change Approach • The Kinda Sorta Not Really Net Change Approach • The As Needed Approach • and more…
  • 10. The Net Change Approach https://sync.server.com/allmystuff?since=20180211
  • 11. The Net Change Approach • Updates data from known point in time • More efficient implementation, more complex logic required • Uses less network bandwidth • Requires handling of adds, changes, deletes on server and mobile • Requires one source of truth for “since” parameter https://sync.server.com/allmystuff?since=20180211
  • 12. The Net Change Approach • Process: • Make API calls - receive lists of “red,” “green,” and “blue” objects with actions identified • Iterate each list - perform add / change / delete as needed to local storage • Update UI
  • 13. The Net Change Approach • Prepare API Call: static func urlRequest(for url:URL, parameters:[String:String]?) -> URLRequest { var requestUrl = url if let passedParameters = parameters { var queryItems:[URLQueryItem] = [] for (parameterName, parameter) in passedParameters { queryItems.append(URLQueryItem(name: parameterName, value: parameter)) } var requestComponents = URLComponents(url: requestUrl, resolvingAgainstBaseURL: false) if let currentQueryItems = requestComponents?.queryItems { queryItems.append(contentsOf: currentQueryItems) } requestComponents?.queryItems = queryItems if let queryRequestUrl = requestComponents?.url { requestUrl = queryRequestUrl } } let request = URLRequest(url: requestUrl) return request }
  • 14. The Net Change Approach • Make API Call: static func fetchObjects<T:RemoteObject>(of type:T.Type, completionHandler: @escaping (_ results:[T]) -> Void, errorHandler: @escaping (_ error:Error) -> Void) -> Void { let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL) let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil), completionHandler: { (data, response, error) in if let actualError = error { DispatchQueue.main.async { errorHandler(actualError) } return } if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { DispatchQueue.main.async { errorHandler(NetworkingError.unexpectedHTTPStatusCode) } return } … }) fetchTask?.resume() }
  • 15. The Net Change Approach • Ingest Data: do { guard let payloadData = data, let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any], let resultsPayload = payload["results"] as? [[String:Any]] else { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } let results = T.ingest(json: resultsPayload) DispatchQueue.main.async { completionHandler(results) } } catch { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return }
  • 16. The Net Change Approach do { guard let payloadData = data, let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any], let resultsPayload = payload["results"] as? [[String:Any]] else { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } let results = T.ingest(json: resultsPayload) DispatchQueue.main.async { completionHandler(results) } } catch { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } static func fetchObjects<T:RemoteObject>(of type:T.Type, completionHandler: @escaping (_ results:[T]) -> Void, errorHandler: @escaping (_ error:Error) -> Void) -> Void { let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL) let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil), completionHandler: { (data, response, error) in if let actualError = error { DispatchQueue.main.async { errorHandler(actualError) } return } if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { DispatchQueue.main.async { errorHandler(NetworkingError.unexpectedHTTPStatusCode) } return } … }) fetchTask?.resume() } static func urlRequest(for url:URL, parameters:[String:String]?) -> URLRequest { var requestUrl = url if let passedParameters = parameters { var queryItems:[URLQueryItem] = [] for (parameterName, parameter) in passedParameters { queryItems.append(URLQueryItem(name: parameterName, value: parameter)) } var requestComponents = URLComponents(url: requestUrl, resolvingAgainstBaseURL: false) if let currentQueryItems = requestComponents?.queryItems { queryItems.append(contentsOf: currentQueryItems) } requestComponents?.queryItems = queryItems if let queryRequestUrl = requestComponents?.url { requestUrl = queryRequestUrl } } let request = URLRequest(url: requestUrl) return request } do { guard let payloadData = data, let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any], let resultsPayload = payload["results"] as? [[String:Any]] else { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } let results = T.ingest(json: resultsPayload) DispatchQueue.main.async { completionHandler(results) } } catch { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } static func fetchObjects<T:RemoteObject>(of type:T.Type, completionHandler: @escaping (_ results:[T]) -> Void, errorHandler: @escaping (_ error:Error) -> Void) -> Void { let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL) let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, parameters: nil), completionHandler: { (data, response, error) in if let actualError = error { DispatchQueue.main.async { errorHandler(actualError) } return } if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { DispatchQueue.main.async { errorHandler(NetworkingError.unexpectedHTTPStatusCode) } return } … }) fetchTask?.resume() } do { guard let payloadData = data, let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String:Any], let resultsPayload = payload["results"] as? [[String:Any]] else { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } let results = T.ingest(json: resultsPayload) DispatchQueue.main.async { completionHandler(results) } } catch { DispatchQueue.main.async { errorHandler(NetworkingError.invalidJSONPayload) } return } static func fetchObjects<T:RemoteObject>(of type:T.Type, completionHandler: @escaping (_ results:[T]) -> Void, errorHandler: @escaping (_ error:Error) -> Void) -> Void { let fetchURL = type.urlForList(from: AppEnvironment.APIBaseURL) let fetchTask = APIManager.authSession?.dataTask(with: type.urlRequest(for: fetchURL, param completionHandler: { (data, response, error) in if let actualError = error { DispatchQueue.main.async { errorHandler(actualError) } return } if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { DispatchQueue.main.async { errorHandler(NetworkingError.unexpectedHTTPStatusCode) } return } … }) fetchTask?.resume() }
  • 17. From-Device Syncing Approaches • The Online Only Approach • The Persist-The-Network-Call Approach • The Offline First Queue Approach • and more…
  • 18. Complications to REST API Syncing • Offline mode • Queueing uploads • Dependencies • Must fetch one entity before fetching another • Must create one entity before creating another • Model changes • Sync API changes with releases • Support older versions
  • 19. REST API Syncing Summary
  • 21. We didn’t need to write syncing code… any more?!
  • 22. Eventually Consistent DB • Server Database (can be NoSQL) • Sync Server • Mobile DB
  • 23. Eventually Consistent DBs • Examples: • Couchbase • Realm • Google Firebase • AWS AppSync • IBM Cloudant • others?
  • 24. The idea: Couchbase Server Sync Server Legacy DB Services Couchbase Lite
  • 25. The idea: Couchbase Server Sync Server Legacy DB Services Couchbase Lite
  • 26. The idea: Couchbase Server Sync Server Legacy DB Services Couchbase Lite
  • 27. The idea: Couchbase Server Couchbase Lite Sync Server Legacy DB Services
  • 28. demo
  • 29.
  • 30.
  • 31.
  • 32. What just happened: Couchbase Server Sync Server Legacy DB Services Couchbase Lite
  • 33.
  • 34. How do we implement? • Model Objects • Data Controller • Fetching Data for Display • Handling Refresh on Change • Adding Data • Deleting Data
  • 35. Model Objects struct BlueBar:Codable { var userId: String var blueIntDetail: Int var type: String var addedBy: String } struct RedBar:Codable { var userId: String var redIntDetail: Int var type: String var addedBy: String } struct GreenBar:Codable { var userId: String var greenIntDetail: Int var type: String var addedBy: String }
  • 36. Data Controller init() { do { database = try Database(name: "rethinking") } catch { print("Unable to initialize Couchbase database 'rethinking'") } }
  • 37. Data Controller (cont) func setUpSyncing(at url:URL) { guard let database = database else { print("No Couchbase database, cannot start syncing") return } let endpoint = URLEndpoint(url: url) let config = ReplicatorConfiguration(database: database, target: endpoint) config.replicatorType = .pushAndPull config.continuous = true config.authenticator = BasicAuthenticator(username: "demo_user", password: "password") config.channels = ["demo_user"] self.replicator = Replicator(config: config) //Set up replication change listeners //Database.setLogLevel(.verbose, domain: .replicator) //Database.setLogLevel(.verbose, domain: .network) self.replicator?.start() }
  • 38. Fetching Data for Display func fetchGreenBars() throws -> [GreenBar] { let documentType = "greenbars" var fetchedGreenBars: [GreenBar] = [] guard let database = self.database else { throw DataControllerModelError.noDatabaseError } let greenBarQuery = QueryBuilder.select(SelectResult.all()) .from(DataSource.database(database)) .where(Expression.property("type").equalTo(Expression.string(documentType))) if greenBarQueryListenerToken == nil { greenBarQueryListenerToken = greenBarQuery.addChangeListener({ [weak self] (change) in self?.greenBarRefreshHandler?() }) } do { for result in try greenBarQuery.execute() { let resultDict = result.toDictionary() if let dataDict = resultDict["rethinking"] { let data = try JSONSerialization.data(withJSONObject: dataDict, options: .prettyPrinted) let decodedObject = try self.jsonDecoder.decode(GreenBar.self, from: data) fetchedGreenBars.append(decodedObject) } } } catch { print(error) } return fetchedGreenBars }
  • 39. Handling Refresh on Change dataController?.greenBarRefreshHandler = { [weak self] in do { self?.greenBars = try self?.dataController?.fetchGreenBars() ?? [] } catch { print("Encounted error attempting to fetch bar records") } self?.reloadSection(for: .green) }
  • 40. Adding Data func add(greenBar: GreenBar) throws { guard let database = self.database else { throw DataControllerModelError.noDatabaseError } let doc = MutableDocument(id: greenBar.userId) let data = try jsonEncoder.encode(greenBar) guard let json = try JSONSerialization.jsonObject(with: data) as? [String:Any] else { throw DataControllerModelError.cannotSerializeJSONError } doc.setData(json) try database.saveDocument(doc) print("Added green bar: (greenBar.greenIntDetail)") }
  • 41. Deleting Data func delete(greenBar: GreenBar) throws { guard let database = self.database else { throw DataControllerModelError.noDatabaseError } guard let documentToDelete = database.document(withID: greenBar.userId) else { throw DataControllerModelError.notFoundError } try database.deleteDocument(documentToDelete) }
  • 42. struct BlueBar:Codable { var userId: String var blueIntDetail: Int var type: String var addedBy: String } struct RedBar:Codable { var userId: String var redIntDetail: Int var type: String var addedBy: String } struct GreenBar:Codable { var userId: String var greenIntDetail: Int var type: String var addedBy: String } init() { do { database = try Database(name: "rethinking") } catch { print("Unable to initialize Couchbase database 'rethinking'") } } func fetchGreenBars() throws -> [GreenBar] { let documentType = "greenbars" var fetchedGreenBars: [GreenBar] = [] guard let database = self.database else { throw DataControllerModelError.noDatabaseError } let greenBarQuery = QueryBuilder.select(SelectResult.all()) .from(DataSource.database(database)) .where(Expression.property("type").equalTo(Expression.string(documentType))) if greenBarQueryListenerToken == nil { greenBarQueryListenerToken = greenBarQuery.addChangeListener({ [weak self] (change) in self?.greenBarRefreshHandler?() }) } do { for result in try greenBarQuery.execute() { let resultDict = result.toDictionary() if let dataDict = resultDict["rethinking"] { let data = try JSONSerialization.data(withJSONObject: dataDict, options: .prettyPrinted) let decodedObject = try self.jsonDecoder.decode(GreenBar.self, from: data) fetchedGreenBars.append(decodedObject) } } } catch { print(error) } return fetchedGreenBars } dataController?.greenBarRefreshHandler = { [weak self] in do { self?.greenBars = try self?.dataController?.fetchGreenBars() ?? [] } catch { print("Encounted error attempting to fetch bar records") } self?.reloadSection(for: .green) } func add(greenBar: GreenBar) throws { guard let database = self.database else { throw DataControllerModelError.noDatabaseError } let doc = MutableDocument(id: greenBar.userId) let data = try jsonEncoder.encode(greenBar) guard let json = try JSONSerialization.jsonObject(with: data) as? [String:Any] else { throw DataControllerModelError.cannotSerializeJSONError } doc.setData(json) try database.saveDocument(doc) print("Added green bar: (greenBar.greenIntDetail)") } func delete(greenBar: GreenBar) throws { guard let database = self.database else { throw DataControllerModelError.noDatabaseError } guard let documentToDelete = database.document(withID: greenBar.userId) else { throw DataControllerModelError.notFoundError } try database.deleteDocument(documentToDelete) }
  • 43. Eventually Consistent DB Advantages • Significant simplification of syncing code • Set up DB, set up syncing call. ~10-20 lines of code • No API calls needed* • Dependencies much easier to handle • Can use nested objects • Thread safety - can ingest model objects in background and *safely* use them in main queue. Try doing that in Core Data! * You might need to do authentication / login with an API call prior to setting up your sync
  • 44. Eventually Consistent DB Advantages (con’t) • Significant simplification of model & ingestion code • No Core Data model needed • No mogenerated ManagedObjectSubclasses • Models can be classes or structs using Codable - very simple to code • Simplification of services • Fewer, less complex endpoints
  • 45. Eventually Consistent DB Disadvantages • Lack of familiarity • Example: client refreshed source DB for staging - crushed our CB instance • Architecture requires different thinking for common use cases • Cannot control priority of syncing - “eventual consistency” • No NSFetchedResultsController-type code (at least in Couchbase Mobile)
  • 46. Eventually Consistent DB Best Use Cases • Handle slowly changing “master” data • For example, organizing data or lists of choices • User created data, especially offline • * Not necessarily images… • Not ideal for large amounts of quickly changing data