Cloud Firestoreにおいて、Relationを表現するとき、DocumentReference型のフィールドを使うべきか、String型のフィールドを使うべき(RDBの外部キーのように他のCollectionのDocumentIdを記録する) かを判断するため、DocumentReference 型の仕様について調査した。
調査結果を受け、The Logs社では以下のデータモデルで運用するものとした
- 基本的にDocumentReference型で、Relationを表現する。 これにより、DocumentReference型が提供するプロパティとメソッドを活用していく
以下、調査の履歴を残しておく。
目次
- DocumentReference 型とは?
- DocumentReference 型の活用
- DocumentReference 型のデータの生成
- 参照先DocumentのRead
- 参照先DocumentのUpdate
- 参照先DocumentのDelete
- Q&A
- Q: DocumentReference 型のフィールドにも自動的にインデックスが作成されるのか?
- A: 作成される
- Q: Arrayフィールドの要素がDocumentReference型のデータであっても、array-containsは使えるのか?
- A: 使える。ただし、パフォーマンスの面で注意を要する
- Reference
DocumentReference 型とは?
DocumentReference 型は、Firestoreのドキュメント内のフィールドに指定できるデータ型のことで、 特定のドキュメントへの参照を表すオブジェクトであり、当該オブジェクトを用いて参照先のドキュメントのRead、Update、Deleteを行うことができる。
DocumentReference 型の活用
DocumentReference 型のデータの生成
doc() メソッドにドキュメント ID を渡すことで、DocumentReference型のデータが得られる
const docRef = db.collection('users').doc('alice');
参照先DocumentのRead
DocumentReference 型から、Documentのデータそのものにアクセスすることはできないが、get() メソッドを使って実体化したDocumentSnapshot 型からアクセスできる
docRef.get().then((doc) => {
if (doc.exists) {
const userData = doc.data(); // ドキュメントのデータを取得
console.log(userData.name); // "alice" など
} else {
// ドキュメントが存在しない場合
console.log("No such document!");
}
}).catch((error) => {
console.error("Error getting document:", error);
});
なお、Document Snapshot 型のデータからは、データそのもの以外に、idやmetadataにもアクセス可能である
DocumentReference 型の主なプロパティ
id: 参照先のドキュメントのID
parent: 参照先のドキュメントが所属するCollection
DocumentSnapshot 型の主なプロパティ・メソッド
id: 参照先のドキュメントのID
data: ドキュメントのデータ
exists: ドキュメントの存在有無
metadata: ドキュメントのメタデータ(作成日時、更新日時など)
参照先DocumentのUpdate
set() メソッド(ドキュメント全体を上書き) や update() メソッド(特定のフィールドを上書き) を使って、参照先のドキュメントのデータを更新できる
docRef.update({
age: 30
})
.then(() => {
console.log('Document updated successfully');
})
.catch((error) => {
console.error('Error updating document:', error);
});
参照先DocumentのDelete
delete() メソッドを使って、参照先のドキュメントを削除できる
docRef.delete()
.then(() => {
console.log('Document deleted successfully');
})
.catch((error) => {
console.error('Error removing document: ', error);
});
Q&A
Q: DocumentReference 型のフィールドにも自動的にインデックスが作成されるのか?
A: 作成される
つまり、comment Collectionのデータとして、user (users CollectionへのDocumentReference型) が存在するとき、uid: user123 が autherのcomment Documentの一覧データを得るために、comment Collection内のドキュメントを全件スキャンが行われることはない。
// uid: user123のユーザーが作成したコメントを取得 (自動的に作成されたインデックスが有効に機能する)
const query = commentsRef.where('user', '==', db.doc('users/user123'));
query.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(error => {
console.error('Error getting documents:', error);
});
また、users Collectionのidデータのインデックスが利用されるため、users Collectionに対する全件スキャンも発生しない。
Q: Arrayフィールドの要素がDocumentReference型のデータであっても、array-containsは使えるのか?
A: 使える。ただし、パフォーマンスの面で注意を要する
// user123が「いいね」した投稿を取得
const userId = 'user123';
const userRef = db.doc('users/' + userId);
const query = postsRef.where('likes', 'array-contains', userRef);
ただし、Firestoreは配列内の要素にIndexを作成できないためこのケースでにおいて、array-containsはpost Collectionのすべてのドキュメントのスキャンが行うことに注意する ( この問題は、配列内の要素がDocumentReferenceでなくても発生する 。 全件スキャンを回避するための方策として、likes情報を別のコレクションに冗長化して保存するなどの方策がある )