FirebaseでModelを作るとダメな理由


FirebaseでModelを作るとダメな理由

Firebase Realtime DatabaseはスキーマレスなDBです。
NoSQLに慣れていない人には、扱いやすいようで扱いにくいデータベースだと思います。さらにFirebaseはMongoDBのようなスキーマレスだけどモデルを定義できるデータベースと違い全くモデルを定義する機能を持っていません。なぜでしょうか。この記事ではなぜモデルを持たない方がいいのかを解説します。

モデルがあるって最高!?

スキーマレスのデータベースが扱いやすいと思うポイント

  • どんな形式でも受け入れてくれる
  • ネストって最高

スキーマレスのデータベースが扱いにくいと思うポイント

  • 今何を触ってるのかわからない感
  • 補完が効いてくれない恐怖

きっともっとまともな理由はすぐに想像つくと思いますが、モデルを作ることでいいこともあれば、それによって縛られることもある訳です。

現実的に、このように書くより

Database.database().reference()
.child("user")
.child("1")
.child("name")
.set("Firebase Man")

こう書きたいと思います。

let user = User()
user.name = "Firebase Man"
user.save()

なぜモデルを提供しないんでしょうか?その理由は、スキーマレスそのものにあると考えられます。以下のユーザー更新を行うフロウを見て下さい。

1. まずは、ユーザーをINSERT

USER TABLE

ID NAME AGE
0 Firebase 3

GROUP TABLE

ID NAME
0 Google

USER_GROUP TABLE

ID USER_ID GROUP_ID
0 0 0

MySQLならこんな感じ

INSERT INTO USER (NAME, AGE)
VALUES ("Firebase", 3)

INSERT INTO GROUP (NAME)
VALUES ("Google")

INSERT INTO USER_GROUP (USER_ID, GROUP_ID)
VALUES (0, 0)

Firebaseならこんな感じ

// Userを作る
let user = ["0": [
  "name": "Firebase",
  "age": "3",
  "groupIDs": [
    0: true  //UserとGroupの相互リレーション
  ]
]]

Database.database().reference()
.child("user")
.set(user)

// Groupを作る
let group = ["0": [
  "name": "Google",
  "userIDs": [
    0: true //UserとGroupの相互リレーション
  ]
]]

Database.database().reference()
.child("group")
.set(group)

2. 次にユーザーを更新

AGEを3->300にます。

MySQLならこんな感じ

INSERT INTO USER (NAME, AGE)
VALUES ("Firebase", 300, 0)

Firebaseならこんな感じ

let user = ["0": [
  "name": "Firebase",
  "age": "3",
  "groupIDs": [
    0: true  //UserとGroupの相互リレーション
  ]
]]

Database.database().reference()
.child("user")
.set(user)

ここが問題です。Firebaseでは次のようにプロパティを明確に指定して更新する必要があります。

Database.database().reference()
.child("user")
.child("0")   // userID
.child("age") // property
.set(300)

なぜ?

FirebaseではN:Nのリレーションに相互参照できるようにデータを保持します。「?」となった方はこちら

前者の更新方法で、二つの端末から同時に更新リクエストがあった場合を考えて見ましょう。
最初のリクエストがgroupIDsに新しいGroupをインサートする処理だったとします。

let user = ["0": [
  "name": "Firebase",
  "age": "3",
  "groupIDs": [
    0: true, //UserとGroupの相互リレーション
    1: true
  ]
]]

Database.database().reference()
.child("user")
.set(user)

次にほんの少し遅れたタイミングでAGEを3->300にするリクエストがきたとします。

let user = ["0": [
  "name": "Firebase",
  "age": "300",
  "groupIDs": [
    0: true  //UserとGroupの相互リレーション
  ]
]]

Database.database().reference()
.child("user")
.set(user)

見事にgroupIDsが上書きされました。Firebaseでモデルを扱うと言うことは、こう言うことです。

でも大丈夫‼️我々にはSaladaがある! (iOSだけ)

SaladaはFirebase Realtime Databaseをモデルで扱うことができるFrameworkです。

こんな感じでGroupUserを扱えます。

let group: Group = Group()
group.name = "iOS Development Team"
group.save { (error, ref) in

    do {
        let user: User = User()
        user.name = "john appleseed"
        user.age = 22
        user.groups.insert(group.id)
        user.save({ (error, ref) in
            group.users.insert(ref.key) // It is updated automatically
        })
    }

    do {
        let user: User = User()
        user.name = "Marilyn Monroe"
        user.age = 22
        user.groups.insert(group.id)
        user.save({ (error, ref) in
            group.users.insert(ref.key) // It is updated automatically
        })
    }
}

Saladaは、Saveが一度しかできません。更新するためにUpdateも用意されていません。値をセットするだけでデータベースもリアルタイムに更新します。例えばユーザーを更新するならこんな感じ

User.observeSingle("userID", eventType: .value) { (user) in
    user?.name = "firebase" // 値をセット瞬間にリアルタイムでオンラインのデータベースを更新します。
}

※ オフラインであっても、値を保持してオンラインになった瞬間に更新します。

SaladaではModelベースで値を更新していません。プロパティベースで値を更新します。