最初に
この記事は、 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