最初に
この記事は、 Swift
で Firestore
の Document Reference Type
を、
どのように扱ったかを共有する記事です。
記載時(2017/12/25)のバージョンは下記の通りです。
対象 | バージョン |
---|---|
Swift | 4.0.3 |
RxSwift | 4.0.0 |
Firestore | 0.9.3 |
Firestore の Reference Type について
Firestore
のReference Type
は、
Realtime Database
(以下、RTDB
) にはない 新しいデータ型 です。
Reference Type
の役割は、コレクション・ドキュメントのパスを指定することにより、
Relational Database
のForeign key
ほど厳密ではないにしろ、
整合性を保つためのキー として役割を担うことができます。
私がRTDB
ではなく、Firestore
を使いたいと思う一番の理由はReference Type
があるということです。
Reference Type を用いたデータの引き方
Reference Type
にはCollection Reference
とDocument Reference
の二種類あります。
今回はDocument Reference
を用いてどのようにデータを引くのかを、
投稿テーブル (posts
) と、ユーザーテーブル (users
) が存在するフィード型アプリを例にご説明いたします。
データ設計と 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
}
}
アプリからのデータの引き方
RxSwift
の Zip
オペレータを利用し、
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
を駆使しすぎるとコストがかかるため、
本当にリレーションが必要な箇所を見極めて設計するのが低コストに抑える必要があると思います。
例えばPost
にタグ機能を付与したいと思った場合は、本当にリレーショナルである必要であるのか考慮します。
場合にはよりますが、私の場合tag
自体、名前の変更は稀なものだと判断し、
Reference Type
ではなく、Object Type
で保持する判断をすると思います。
最後に
Firestore
は現時点では βバージョン ですが、
Reference
型がなくなるということはないと思うので、
Reference
型について知っておいて損はないと思います。
参考
サポートされるデータ型 | Firebase
https://stackoverflow.com/questions/46568850/what-is-firestore-reference-data-type-good-for