LoginSignup
61
35

More than 5 years have passed since last update.

FireStore の Document Reference Type について

Posted at

最初に

この記事は、 SwiftFirestoreDocument Reference Type を、
どのように扱ったかを共有する記事です。

記載時(2017/12/25)のバージョンは下記の通りです。

対象 バージョン
Swift 4.0.3
RxSwift 4.0.0
Firestore 0.9.3

Firestore の Reference Type について

FirestoreReference Typeは、
Realtime Database(以下、RTDB) にはない 新しいデータ型 です。

Reference Typeの役割は、コレクション・ドキュメントのパスを指定することにより、
Relational DatabaseForeign keyほど厳密ではないにしろ、
整合性を保つためのキー として役割を担うことができます。

私がRTDBではなく、Firestoreを使いたいと思う一番の理由はReference Typeがあるということです。

Reference Type を用いたデータの引き方

Reference Type にはCollection ReferenceDocument Referenceの二種類あります。
今回はDocument Referenceを用いてどのようにデータを引くのかを、
投稿テーブル (posts) と、ユーザーテーブル (users) が存在するフィード型アプリを例にご説明いたします。

Screen Shot 2017-12-25 at 14.41.31.png

データ設計と Entity クラス

投稿テーブル (posts) / Firestore

Field Data Type
title String
userReference Reference

投稿モデル (Post) / Swift

userReferenceからデータを取得した後にセットする用に、Userプロパティを用意します。

struct Post {
    let id: String
    let title: String
    let userReference: DocumentReference
    var user: User?

    init(key: String, dictionary: [String: Any]) throws {
        guard let title = dictionary["title"] as? String,
        let userReference = dictionary["userReference"] as? DocumentReference else { throw FirestoreError.initializeFailed }

        self.id = key
        self.title = title
        self.userReference = userReference
    }
}

ユーザーテーブル (users) / Firestore

Field Data Type
name String

ユーザーモデル (User) / Swift

struct User {
    let id: String
    let name: String

    init(key: String, dictionary: [String: Any]) throws {
        guard let name = dictionary["name"] as? String else { throw FirestoreError.initializeFailed }

        self.id = key
        self.name = name
    }
}

アプリからのデータの引き方

RxSwiftZip オペレータを利用し、
Post に紐づく User 情報を結合して取得します。

fileprivate func observeQuery() {
    Firestore.firestore().collection("posts").addSnapshotListener { [unowned self] (snapshot, error) in
        guard let snapshot = snapshot else { return }
        var posts = snapshot.documents.map {
            try! Post(key: $0.documentID, dictionary: $0.data())
        }

        print(posts) // この時点で user は nil

        let observables: [Observable<User>] = posts.map {
            // userReference を元に user を取得する observe を生成
            self.observeUser(documentRef: $0.userReference)
        }
        Observable.zip(observables).subscribe({
            guard let users = $0.element else { return }
            for user in users {
                for (index, post) in posts.enumerated() where post.userReference.documentID == user.id {
                    posts[index].userEntity = user
                }
            }
            print(posts) // user にデータが入っている
        }).disposed(by: self.disposeBag)
    }
}

全体のソースコード

Firestore用の設定を行った後に以下をコピれば動くと思います。
https://gist.github.com/bpyamasinn/138a6e8264cf181bbf01e2cb162bf73e
全体で 100 行ほどなので、簡単にFirestoreの仕組みと、Document Referenceの使い方の参考になると思います。

料金に関する注意点

RTDBと違い、Firestore通信量 ではなく、 通信回数 で料金が発生します。
そのため、Document Referenceを駆使しすぎるとコストがかかるため、
本当にリレーションが必要な箇所を見極めて設計するのが低コストに抑える必要があると思います。

Firebase 製品の料金一覧

例えばPostにタグ機能を付与したいと思った場合は、本当にリレーショナルである必要であるのか考慮します。
場合にはよりますが、私の場合tag自体、名前の変更は稀なものだと判断し、
Reference Typeではなく、Object Typeで保持する判断をすると思います。

最後に

Firestore は現時点では βバージョン ですが、
Reference 型がなくなるということはないと思うので、
Reference 型について知っておいて損はないと思います。

参考

サポートされるデータ型  |  Firebase
https://stackoverflow.com/questions/46568850/what-is-firestore-reference-data-type-good-for

61
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
61
35