前回の記事と同様に以下の記事の内容を整理した。
出てくる例ごとにER図を書いてみると、なんとなくNoSQLわかってきたかも。
基本的な考え方
- 複製
- 複数のDocumentを作成しないよう、あらかじめ展開しておく
- ただし、元データが更新された時は、複数のDocumentで更新が必要
- 集約
- 集計処理は、Firestoreのクラウド機能をつかってサーバ側でおこなう
- 複合キー
- 単に、
userXYZ_postABC
のように、組み合わせて一意のIDになるようにする
- 単に、
- バケット
- コレクションを単一の文書に分割する複製/集約の形式
- 参照したい単位で別途コレクションを作成する。
- グループ コレクション
- 所属しているグループIDがキーとなってる親ドキュメントをもたせる
- 所属が更新するたびに、グループIDが一致するキーのフラグを更新する
例1: ショッピングカート
概要
よくあるショッピングカートの例
- One-to-One: ユーザのカート
- Many-to-Many: ユーザが商品をカートに入れる
- One-to-Many: ユーザが商品を注文
また、商品には在庫数などがある
ER図
データモデル
products/{productID} ... 商品のマスタ - productInfo (any) -- amountInStock (number) ... 商品の在庫情報 users/{userID} -- userInfo (any) ... ユーザの基本情報 ++ orders ... ユーザの注文(1対多) -- items [ ... 注文した商品 { product: productID, qty: 23 } ] carts/{userID} ... ユーザのカート(1対1) -- total (number) ... 集約 - products { ... ユーザがカートに入れた商品(多対多) productId: quantity } ]
例1-1: ショッピングカートのカート部分(多対多)
products/{productID} ... 商品のマスタ - productInfo (any) -- amountInStock (number) ... 商品の在庫情報 users/{userID} -- userInfo (any) ... ユーザの基本情報 carts/{userID} ... ユーザのカート(1対1) -- total (number) ... 集約 - products { ... ユーザがカートに入れた商品(多対多) productId: quantity } ]
例1-2: ショッピングカートの注文部分(明細構造)
products/{productID} ... 商品のマスタ - productInfo (any) -- amountInStock (number) ... 商品の在庫情報 users/{userID} -- userInfo (any) ... ユーザの基本情報 ++ orders ... ユーザの注文(1対多) -- items [ ... 注文した商品 { product: productID, qty: 23 } ]
例2: フォロー
Twitterみたいなフォロー/アンフォローの例。
- 多対多の関係は、
followerID_followedID
で表現 - fieldでそれぞれのIDをもたせて、検索できるようにする
- 各ユーザの情報は、プログラムJOINする
- フォロー数とかは、集約フィールドをもたせる
ER図
データモデル
users/{userID} -- userInfo (any) -- followerCount (number) ... 集約 -- followedCount (number) ... 集約 relationships/{followerID_followedID} ... 多対多 -- followerId (string) -- followedId (string) -- createdAt (timestamp)
クエリ
// ユーザーAがユーザーBをフォロー db.collection('relationships').doc(`${followerId}_${followedId}`); // ユーザーの最新のフォロワー50人を取得: db.collection('relationships') .where('followedId', '==', userId) .orderBy('createdAt', 'desc') .limit(50); // フォローされているすべてのユーザーを取得: db.collection('relationships').where('followerId', '==', userId);
例3: スレッドコメント/階層ツリー
ブログとかへのスレッド化されたコメントの例
ER図
データモデル
posts/{postId} ... pコメントされる記事 ++ comments/{commentB} ... コメント自体 -- createdAt (date) parent: commentA ... 親コメントのID children: [ commentC, commentD ] ... 子コメントのID一覧
クエリ
// ルートコメントを取得する comments.where('parent', '==', null)
例4: タグ/ハッシュタグ
ER図
データモデル
tweets/{tweetId} ... ツイート -- content (string) -- tags: { ... タグを含むかのフラグ(多対多) angular: true, firebase: true } tags/{content} ... タグの一覧 -- content (string) -- tweetCount (number) ... 集約: タグを持つツイート数
クエリ
// 特定のタグのすべてのツイートを取得 db.collection('tweets').where('tags.angular', '==', true); // Or db.collection('tweets').orderBy('tags.angular'); // 最も人気のあるタグを取得 db.collection('tags').orderBy('tweetCount', 'desc'); // 特定のタグを取得 tagId = 'SomeCoolTag'.toLowerCase(); db.doc('tags/' + tagId);
以上!!
参考にしたサイト様
- Advanced Data Modeling With Firestore by Example | AngularFirebase
- Cloud Firestore データモデル | Firebase
- Firestoreの参照型によるリレーションを試す - Crieit
- 脱RDB脳!Firebase Databse導入のために考えた4つのポイント - Qiita
- Firestore で いいね順(Score順)Sort + Paging するポイント - Qiita
- 【Firebase】Cloud Firestoreのデータ構造の決め方をFirebaseの動画から学ぶ - Qiita
- Cloud FirestoreとFirebase Cloud Storageを使ってソーシャル機能を実装する方法 - Qiita
- Cloud Firestoreの勘所 パート3 — セキュリティルール – google-cloud-jp – Medium
- Cloud FunctionsからCloud Firestoreを使う - ゆるふわ技術日誌
- Firebase Firestoreのcollection groupを使ってみる - Qiita