$30 off During Our Annual Pro Sale. View Details »

APIからFirebase Realtime Databaseへ - クライアントサイドから見るサーバーレスアーキテクチャー

d_date
November 03, 2017

APIからFirebase Realtime Databaseへ - クライアントサイドから見るサーバーレスアーキテクチャー

2018/11/3 serverlessconf Tokyo
Firebase Realtime Database / Cloud Firestoreの話

d_date

November 03, 2017
Tweet

More Decks by d_date

Other Decks in Programming

Transcript

  1. Replace API to Firebase Database
    - The view from client side
    Daiki Matsudate / @d_date
    2017/11/03 / Serverlessconf Tokyo 2017

    View Slide

  2. Daiki Matsudate
    iOS Mobile App Engineer
    @d_date

    View Slide

  3. Tokyo
    2018/3/1 - 3

    View Slide

  4. Firebase

    View Slide

  5. Firebase

    View Slide

  6. Cloud Messaging

    View Slide

  7. View Slide

  8. View Slide

  9. 31 Oct. at Amsterdam
    #FirebaseSummit

    View Slide

  10. • Prediction
    • A/B Testing
    • Integrate Crashlytics into Firebase
    #FirebaseSummit

    View Slide

  11. #FirebaseSummit

    View Slide

  12. Agenda
    • Firebase Realtime Database
    • Feature
    • Introduce to Mobile App
    • Architecture
    • Testing
    • ???

    View Slide

  13. Firebase Database

    View Slide

  14. • NoSQL cloud database
    • Realtime Data Sync with JSON Tree
    • Available data on offline
    Firebase Database

    View Slide

  15. Firebase Database

    View Slide

  16. Replace API to Realtime Database
    Case Study

    View Slide

  17. Replace API to Realtime Database
    API
    Client (Native App) Server

    View Slide

  18. Client (Native App) Server
    Firebase
    Database
    Replace API to Realtime Database

    View Slide

  19. Replace API to Realtime Database
    • use func observeSingleEvent(:_) in FirebaseDatabase

    View Slide

  20. Replace API to Realtime Database
    • Reduce building API man-hours
    • No more performance tunings
    • Easy to handle data in offline
    Purpose

    View Slide

  21. Client (Native App) Server
    Firebase
    Database
    Replace API to Realtime Database

    View Slide

  22. Client (Native App)
    Firebase
    Database
    Replace API to Realtime Database

    View Slide

  23. NoSQL

    View Slide

  24. NoSQL = Denormalization

    View Slide

  25. Denormalization in NoSQL
    {
    "users": {
    "user1": {
    "name": "Alice"
    },
    "user2": {
    "name": "Bob"
    }
    },
    "links": {
    "link1": {
    "title": "Example",
    "href": "http://example.org",
    "submitted": "user1"
    }
    },
    "comments": {
    "comment1": {
    "link": "link1",
    "body": "This is awesome!",
    "author": "user2"
    }
    }
    }

    View Slide

  26. Denormalization in NoSQL
    {
    "users": {
    "user1": {
    "name": "Alice"
    },
    "user2": {
    "name": "Bob"
    }
    },
    "links": {
    "link1": {
    "title": "Example",
    "href": "http://example.org",
    "submitted": "user1"
    }
    },
    "comments": {
    "comment1": {
    "link": "link1",
    "body": "This is awesome!",
    "author": "user2"
    }
    }
    }

    View Slide

  27. Denormalization in NoSQL
    {
    "users": {
    "user1": {
    "name": "Alice"
    },
    "user2": {
    "name": "Bob"
    }
    },
    "links": {
    "link1": {
    "title": "Example",
    "href": "http://example.org",
    "submitted": "user1"
    }
    },
    "comments": {
    "comment1": {
    "link": "link1",
    "body": "This is awesome!",
    "author": "user2"
    }
    }
    }
    Join data in client

    View Slide

  28. View Slide

  29. JOIN data in client in Swift
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })

    View Slide

  30. Not Swifty!!
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })
    “Stringly“ typed
    Pyramid of Death

    View Slide

  31. Easy to replace from API?
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })
    Need reference of database

    View Slide

  32. View Slide

  33. To make friend with NoSQL in Swift

    View Slide

  34. To make friend with NoSQL in Swift
    • Re-Design Architecture

    • Introduce Reactive Programming

    View Slide

  35. Re-Design Architecture

    View Slide

  36. View Slide

  37. View Slide

  38. View Slide

  39. View Slide

  40. View Slide

  41. !Architecture → Ideal State
    "Ideal State → Architecture

    View Slide

  42. Ideal State
    • Data from Firebase ≠ Data in app -> Need translation
    • Easy to replace from API to Firebase
    -> I/F is same as API, then want to replace DAO

    View Slide

  43. Replaceable Data Access Object
    Entity
    Data Access
    to API
    Data Access
    Interface
    (Repository)
    Server

    View Slide

  44. Replaceable Data Access Object
    Entity
    Data Access
    to Firebase
    Data Access
    Interface
    (Repository)

    View Slide

  45. Translation
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    View

    View Slide

  46. Requirement for Architecture
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    View

    View Slide

  47. What is this architecture?
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    Presenter
    View

    View Slide

  48. View Slide

  49. How to resolve Pyramid of Death
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })
    Pyramid of Death

    View Slide

  50. View Slide

  51. How to resolve Pyramid of Death
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })

    View Slide

  52. protocol Entity: Codable {}
    struct UserEntity: Entity {
    let name: String
    }
    struct LinkEntity: Entity {
    let title: String
    let href: URL
    let submitted: String
    }
    struct CommentEntity: Entity {
    let link: String
    let body: String
    let author: String
    }
    Entity
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  53. Data Access + RxSwift
    extension DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    self.observeSingleEvent(of: .value, with: { (snapshot) in
    guard let jsonObject = snapshot.value else {
    observer(.error(ProviderError.valueNotExist))
    return
    }
    do {
    let data = try JSONSerialization.data(withJSONObject: jsonObject)
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch(let error) {
    observer(.error(error))
    }
    })
    return Disposables.create()
    })
    }
    }

    View Slide

  54. Data Access + RxSwift
    extension DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    self.observeSingleEvent(of: .value, with: { (snapshot) in
    guard let jsonObject = snapshot.value else {
    observer(.error(ProviderError.valueNotExist))
    return
    }
    do {
    let data = try JSONSerialization.data(withJSONObject: jsonObject)
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch(let error) {
    observer(.error(error))
    }
    })
    return Disposables.create()
    })
    }
    }
    If value not exist,
    invoke error

    View Slide

  55. Data Access + RxSwift
    extension DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    self.observeSingleEvent(of: .value, with: { (snapshot) in
    guard let jsonObject = snapshot.value else {
    observer(.error(ProviderError.valueNotExist))
    return
    }
    do {
    let data = try JSONSerialization.data(withJSONObject: jsonObject)
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch(let error) {
    observer(.error(error))
    }
    })
    return Disposables.create()
    })
    }
    }
    If fail to serialize JSON,
    invoke error

    View Slide

  56. Data Provider
    class DataProvider {
    static let shared = DataProvider()
    private init() {}
    lazy var ref: DatabaseReference = {
    return Database.database().reference()
    }()
    func getUser(key: String) -> Single {
    return ref.child("users").child(key).get()
    }
    func getLink(key: String) -> Single {
    return ref.child("links").child(key).get()
    }
    func getComments() -> Single {
    return ref.child("comments").get()
    }
    }

    View Slide

  57. Data Provider
    class DataProvider {
    static let shared = DataProvider()
    private init() {}
    lazy var ref: DatabaseReference = {
    return Database.database().reference()
    }()
    func getUser(key: String) -> Single {
    return ref.child("users").child(key).get()
    }
    func getLink(key: String) -> Single {
    return ref.child("links").child(key).get()
    }
    func getComments() -> Single {
    return ref.child("comments").get()
    }
    }
    Singleton in Swift

    View Slide

  58. Data Provider
    class DataProvider {
    static let shared = DataProvider()
    private init() {}
    lazy var ref: DatabaseReference = {
    return Database.database().reference()
    }()
    func getUser(key: String) -> Single {
    return ref.child("users").child(key).get()
    }
    func getLink(key: String) -> Single {
    return ref.child("links").child(key).get()
    }
    func getComments() -> Single {
    return ref.child("comments").get()
    }
    }
    Retain DatabaseReference

    View Slide

  59. Data Provider
    class DataProvider {
    static let shared = DataProvider()
    private init() {}
    lazy var ref: DatabaseReference = {
    return Database.database().reference()
    }()
    func getUser(key: String) -> Single {
    return ref.child("users").child(key).get()
    }
    func getLink(key: String) -> Single {
    return ref.child("links").child(key).get()
    }
    func getComments() -> Single {
    return ref.child("comments").get()
    }
    }
    Get Entity from Database,
    then return Single

    View Slide

  60. Data Access
    struct UserRepository {
    let dataStore: LinkDataStore
    func get(userId: String) -> Single {
    return dataStore.get(key: userId)
    }
    }
    struct UserDataStore {
    let provider = DataProvider.shared
    func get(key: String) -> Single {
    return provider.getUser(key: key)
    }
    }
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  61. Data Access
    struct UserRepository {
    let dataStore: LinkDataStore
    func get(userId: String) -> Single {
    return dataStore.get(key: userId)
    }
    }
    struct UserDataStore {
    let provider = DataProvider.shared
    func get(key: String) -> Single {
    return provider.getUser(key: key)
    }
    }
    I/F for data access
    Implementation for data
    access
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  62. Interactor (UseCase)
    struct UseCase {
    let commentsRepository: CommentRepository
    let usersRepository: UserRepository
    let linksRepository: LinkRepository
    func getComments() -> Single<[CommentModel]> {
    return commentsRepository.get()
    .map { (comment) -> [CommentModel] in
    let link = self.getLink(linkId: comment.link)
    let author = self.getUser(userId: comment.author)
    return Single<(LinkModel, UserModel)>.zip(link, author) {
    ($0, $1)
    }).map(translator: CommentTranslator())
    }
    }
    }
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  63. Before
    self.ref.child("comments").observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
    guard let strongSelf = self else { return }
    guard let comments = snapshot.value as? [String: Any] else { return }
    comments.forEach({ (key, value) in
    guard let comment = value as? [String: Any] else { return }
    let linkKey: String = comment["link"] as! String // "link1"
    let authorKey: String = comment["author"] as! String // "user2"
    strongSelf.ref.child("links").child(linkKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let link = snapshot.value as? [String: Any] else { return }
    let submittedKey: String = link["submitted"] as! String // "user1"
    strongSelf.ref.child("users").child(submittedKey).observeSingleEvent(of: .value, with:
    { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Alice"
    print(name)
    })
    })
    strongSelf.ref.child("users").child(authorKey).observeSingleEvent(of: .value, with: { (snapshot) in
    guard let user = snapshot.value as? [String: Any] else { return }
    let name: String = user["name"] as! String // “Bob”
    print(name)
    })
    })
    })

    View Slide

  64. Presenter
    class Presenter {
    let useCase: UseCase
    init(useCase: UseCase) {
    self.useCase = useCase
    }
    func getComments() -> Single<[CommentModel]> {
    return useCase.getComments()
    }
    }
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  65. View
    class ViewController: UIViewController {
    weak var presenter: Presenter!
    private let disposeBag = DisposeBag()
    func inject(presenter: Presenter) {
    self.presenter = presenter
    }
    override func viewDidLoad() {
    super.viewDidLoad()
    presenter.getComments()
    .subscribe(onSuccess: { (models) in
    //Handling model
    }, onError: { (error) in
    //Handling Error
    }).disposed(by: disposeBag)
    }
    }
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View

    View Slide

  66. View
    class ViewController: UIViewController {
    weak var presenter: Presenter!
    private let disposeBag = DisposeBag()
    func inject(presenter: Presenter) {
    self.presenter = presenter
    }
    override func viewDidLoad() {
    super.viewDidLoad()
    presenter.getComments()
    .subscribe(onSuccess: { (models) in
    //Handling model
    }, onError: { (error) in
    //Handling Error
    }).disposed(by: disposeBag)
    }
    }
    Entity
    Data Access
    Data
    Access
    Interactor
    Model
    Presenter
    View
    Focus on UI Handling

    View Slide

  67. Side Effect

    View Slide

  68. Side Effect
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    Presenter
    View

    View Slide

  69. Side Effect - Easy Unit Testing
    Entity
    Data Access
    Interface
    Mock Object
    Interactor
    Model
    Presenter
    View
    Easy to replace

    View Slide

  70. class DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    guard let bundlePath = Bundle(for: type(of: self)).path(forResource: "Stub", ofType: "bundle") else {
    observer(.error(SerializeError.missingBundle))
    return Disposables.create()
    }
    let jsonName = String(describing: T.self)
    guard let path = Bundle(path: bundlePath)?.path(forResource: jsonName, ofType: "json") else {
    observer(.error(SerializeError.missingJson(jsonName: jsonName)))
    return Disposables.create()
    }
    do{
    let data = try Data(contentsOf: URL(fileURLWithPath: path))
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch {
    observer(.error(error))
    }
    return Disposables.create()
    })
    }
    }
    Side Effect - Easy Unit Testing

    View Slide

  71. class DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    guard let bundlePath = Bundle(for: type(of: self)).path(forResource: "Stub", ofType: "bundle") else {
    observer(.error(SerializeError.missingBundle))
    return Disposables.create()
    }
    let jsonName = String(describing: T.self)
    guard let path = Bundle(path: bundlePath)?.path(forResource: jsonName, ofType: "json") else {
    observer(.error(SerializeError.missingJson(jsonName: jsonName)))
    return Disposables.create()
    }
    do{
    let data = try Data(contentsOf: URL(fileURLWithPath: path))
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch {
    observer(.error(error))
    }
    return Disposables.create()
    })
    }
    }
    Side Effect - Easy Unit Testing
    FackObject

    View Slide

  72. class DatabaseReference {
    func get() -> Single {
    return Single.create(subscribe: { (observer) in
    guard let bundlePath = Bundle(for: type(of: self)).path(forResource: "Stub", ofType: "bundle") else {
    observer(.error(SerializeError.missingBundle))
    return Disposables.create()
    }
    let jsonName = String(describing: T.self)
    guard let path = Bundle(path: bundlePath)?.path(forResource: jsonName, ofType: "json") else {
    observer(.error(SerializeError.missingJson(jsonName: jsonName)))
    return Disposables.create()
    }
    do{
    let data = try Data(contentsOf: URL(fileURLWithPath: path))
    let entity = try JSONDecoder().decode(T.self, from: data)
    observer(.success(entity))
    } catch {
    observer(.error(error))
    }
    return Disposables.create()
    })
    }
    }
    Return Entity created from
    Stub JSON
    Side Effect - Easy Unit Testing
    FackObject

    View Slide

  73. Tips - Realtime update or not
    lazy var realtimeRef: DatabaseReference = {
    let ref = Database.database().reference()
    ref.keepSynced(true)
    return ref
    }()
    func getFavorite(userId: String) -> Single {
    return realtimeRef.child(userId).child("favorites").get()
    }

    View Slide

  74. Tips - Realtime update or not
    lazy var realtimeRef: DatabaseReference = {
    let ref = Database.database().reference()
    ref.keepSynced(true)
    return ref
    }()
    func getFavorite(userId: String) -> Single {
    return realtimeRef.child(userId).child("favorites").get()
    }
    Reflect database update
    immediately

    View Slide

  75. Pros/Cons
    • Reduce building API man-hours
    • No more performance tunings
    • Easy to handle data in offline
    Pros

    View Slide

  76. Pros/Cons
    • Reduce building API man-hours
    • No more performance tunings
    • Easy to handle data in offline
    Pros
    #

    View Slide

  77. Pros/Cons
    • Must have join logic in client side
    • Need more money $ to build in some case
    • Data cache system doesn’t suit for your app in some case
    • Sorting and Query are hard to use
    • Scaling requires sharding
    (100,000 concurrent connections & 1,000 writes/sec.)
    Cons

    View Slide

  78. Pros/Cons
    • Must have join logic in client side
    • Need more money $ to build in some case
    • Data cache system doesn’t suit for your app in some case
    • Sorting and Query are hard to use
    • Scaling requires sharding
    (100,000 concurrent connections & 1,000 writes/sec.)
    Cons
    %

    View Slide

  79. Alternatives
    • Realtime Database + Cloud Functions
    • Building API server (Old way)
    • ???

    View Slide

  80. View Slide

  81. One more thing…

    View Slide

  82. Cloud Firestore

    View Slide

  83. • Stores data in documents organized in collections
    • Sending data with protobuf, which have more types
    • Introduced reference type, so requires less denormalization and data
    flattening
    • Indexed queries with compound sorting and filtering
    Cloud Firestore

    View Slide

  84. Cloud Firestore

    View Slide

  85. Cloud Firestore
    Reference type

    View Slide

  86. What will be changed on architecture
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    Presenter
    View

    View Slide

  87. What will be changed on architecture
    Entity
    Data Access
    Interface
    Data Access
    to Firebase
    Interactor
    Model
    Presenter
    View

    View Slide

  88. Recap
    • Serverless architecture ( mBaaS / SaaS and etc. ) is also
    effective to use on native apps
    • API server can be replaced by Firebase Database / Cloud
    firestore in some case
    • Replaceable DAO and separate Entity and Model in app is
    important
    • With side effect, easy to test modules

    View Slide

  89. If you have Questions…
    Daiki Matsudate
    [email protected]
    twitter: @d_date
    GitHub: @d-date

    View Slide

  90. Enjoy! Your serverless

    View Slide

  91. References
    • Clean Code: A Handbook of Agile Software Craftsmanship
    by Robert C. Martin
    • https://firebase.google.com/docs/firestore/rtdb-vs-firestore
    • https://firebase.google.com/docs/firestore/firestore-for-
    rtdb
    • https://speakerdeck.com/bpyamasinn/api-wo-firebase-
    realtime-database-niyi-xing-siteqi-fu-itakoto-ver2

    View Slide