firebase-cloud-firestore について
FlutterアプリでCloud Firestoreを実装する開発者向けのガイドです。データ構造、クエリ、リアルタイム更新、セキュリティルール、スケーリングを扱います。
主な機能
- 01ドキュメントとサブコレクションの設計
- 02フィルター検索とカーソルページネーション
- 03一括書き込みとトランザクション
- 04リアルタイムリスナー
- 05インデックス、ホットスポット、トラフィックの指針
- 06セキュリティルールと入力検証
活用シーン
- 01階層的なアプリデータの設計
- 02絞り込み済みコレクションクエリのページ分割
- 03複数ドキュメントのアトミック更新
- 04セキュリティルールによるアクセス制限
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ evanca/ flutter-ai-rules --skill firebase-cloud-firestoreインストール後、Claude Code で/firebase-cloud-firestoreを入力して読み込まれたか確認します。
詳細
Firebase Cloud Firestore スキル
このスキルでは、データモデリング、クエリ、リアルタイム更新、セキュリティルール、スケール最適化を含め、Flutter アプリケーションで Cloud Firestore を正しく実装する方法を定義します。
使用する場面
次の場合にこのスキルを使用します。
- Flutter プロジェクトで Cloud Firestore をセットアップして構成するとき。
- ドキュメントやコレクションの構造を設計したり、サブコレクションを計画したりするとき。
- 読み取り、書き込み、バッチ、トランザクションの操作を実行するとき。
- リアルタイムリスナーやページネーション付きクエリを実装するとき。
- スケールを最適化し、書き込みのホットスポットを回避するとき。
- Firestore セキュリティルールを作成またはデバッグするとき。
1. データベースの選択
次の場合は Cloud Firestore を選択します。
- サブコレクションを含む、豊富で階層的なデータモデルが必要な場合。
- 複雑なクエリが必要な場合:フィルターの連結、プロパティによるフィルタリングと並べ替えの組み合わせ。
- データベース内の任意の場所からデータを原子的に読み書きするトランザクションが必要な場合。
- 高可用性(通常の稼働率は 99.999%)またはミッションクリティカルな信頼性が必要な場合。
- 数百万の同時ユーザーに合わせた自動スケーリングが必要な場合。
単純なデータモデルで、単純な検索と極めて低遅延の同期(通常の応答時間は 10ms 未満)が必要な場合は、代わりに Realtime Database を使用します。
2. セットアップと構成
flutter pub add cloud_firestore
import 'package:cloud_firestore/cloud_firestore.dart';
final db = FirebaseFirestore.instance; // after Firebase.initializeApp()
ロケーション:
- ユーザーとコンピューティングリソースに最も近いデータベースロケーションを選択します。
- 重要なアプリでは、可用性と耐久性を最大化できる マルチリージョン ロケーションを使用します。
- コストと書き込みレイテンシを抑えるには、リージョン ロケーションを使用します。
iOS/macOS: ビルド時間を短縮するため、プリコンパイル済みフレームワークを検討します。
pod 'FirebaseFirestore',
:git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git',
:tag => 'IOS_SDK_VERSION'
オフライン永続化 はモバイルでデフォルトで有効です。キャッシュサイズを構成します。
FirebaseFirestore.instance.settings = const Settings(
persistenceEnabled: true,
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);
3. ドキュメント構造
- Firestore のパスで特別な意味を持つドキュメント ID
.と..は避けます。 - パス区切り文字であるスラッシュ(
/)をドキュメント ID に含めないでください。 - 単調増加するドキュメント ID(例:
Customer1、Customer2)は使用しないでください。書き込みのホットスポットが発生します。 - 可能な場合は、Firestore の 自動生成ドキュメント ID を使用します。
final docRef = await db.collection("users").add({
'name': 'Ada Lovelace',
'email': '[email protected]',
'created_at': FieldValue.serverTimestamp(),
});
print('Created document with ID: ${docRef.id}');
- フィールド名には次の文字を使用しないでください(エスケープが余分に必要になります):
.[]*` - 複雑で階層的なデータを整理するには、深くネストされたオブジェクトではなく、ドキュメント内の サブコレクション を使用します。
4. インデックス作成
- Firestore クエリにはデフォルトでインデックスが作成されます。クエリのパフォーマンスはデータセットのサイズではなく、結果セットのサイズに比例します。
- 書き込みレイテンシとストレージコストを削減するため、コレクションレベルのインデックス除外 を設定します。
- 降順インデックスと配列インデックスが不要なフィールドでは、それらを無効にします。
- クエリに使用しない、値の長い文字列フィールドはインデックスから除外します。
- タイムスタンプなどの連続した値を持つフィールドがクエリに使用されない場合は、インデックスから除外します。これにより、インデックスの書き込み上限である毎秒 500 回を回避できます。
- TTL フィールドには単一フィールドのインデックス除外を追加します。
- クエリに使用しない大きな配列フィールドやマップフィールドはインデックスから除外します。これにより、ドキュメントあたり 40,000 個というインデックスエントリの上限を回避できます。
5. 読み取りと書き込みの操作
コレクション内のすべてのドキュメントを読み取る
final querySnapshot = await db.collection("users").get();
for (var doc in querySnapshot.docs) {
print("${doc.id} => ${doc.data()}");
}
フィルターを使用したクエリ
final query = db.collection("users")
.where("age", isGreaterThanOrEqualTo: 18)
.orderBy("age")
.limit(20);
final results = await query.get();
カーソルベースのページネーション
// First page
final first = db.collection("cities").orderBy("name").limit(25);
final firstSnapshot = await first.get();
// Next page using last document as cursor
final lastDoc = firstSnapshot.docs.last;
final next = db.collection("cities")
.orderBy("name")
.startAfterDocument(lastDoc)
.limit(25);
- ページネーションにオフセットを使用しないでください。スキップしたドキュメントの取得と課金を避けるため、カーソルを使用します。
サーバータイムスタンプを使用した書き込み
await db.collection("users").doc("user_1").set({
'name': 'Grace Hopper',
'updated_at': FieldValue.serverTimestamp(),
});
バッチ書き込み(原子的、最大 500 操作)
final batch = db.batch();
batch.set(db.collection("cities").doc("LA"), {'name': 'Los Angeles'});
batch.update(db.collection("cities").doc("SF"), {'population': 860000});
batch.delete(db.collection("cities").doc("OLD"));
await batch.commit();
トランザクション
await db.runTransaction((transaction) async {
final snapshot = await transaction.get(db.collection("counters").doc("visits"));
final currentCount = snapshot.get("count") as int;
transaction.update(snapshot.reference, {"count": currentCount + 1});
});
- ドキュメント検索とクエリなど、独立した操作は順次ではなく 並列 に実行します。
- 書き込みレート制限に注意してください。1 ドキュメントあたり約 1 秒に 1 回です。
- 大量のドキュメントを書き込む場合は、原子的なバッチライターではなく 一括ライター を使用します。
6. スケーリングを考慮した設計
- 辞書順で近接するドキュメントに対する読み取りや書き込みを高頻度で行わないでください(ホットスポット化)。
- (タイムスタンプなどの)単調増加するフィールドを持つ新しいドキュメントを非常に高い頻度で作成しないでください。
- コレクション内のドキュメントを高頻度で削除しないでください。
- 高頻度でデータベースに書き込む場合は、トラフィックを徐々に増やしてください。5 分かけて増やします。
- 最近削除されたデータを飛ばしてしまうクエリは避け、正しい開始位置を見つけるには
start_atを使用してください。 - 競合を避けるため、異なるドキュメントパスに書き込みを分散してください。
- Firestore は、約 100 万件の同時接続と 1 秒あたり 10,000 件の書き込みに自動でスケールします。
7. リアルタイム更新
final subscription = db.collection("messages")
.where("room", isEqualTo: "general")
.orderBy("timestamp", descending: true)
.limit(50)
.snapshots()
.listen((querySnapshot) {
for (var change in querySnapshot.docChanges) {
switch (change.type) {
case DocumentChangeType.added:
print("New message: ${change.doc.data()}");
break;
case DocumentChangeType.modified:
print("Modified: ${change.doc.data()}");
break;
case DocumentChangeType.removed:
print("Removed: ${change.doc.id}");
break;
}
}
});
// Detach when no longer needed:
subscription.cancel();
- 同時に実行するリアルタイムリスナーの数を制限してください。
- メモリリークや不要な読み取りを避けるため、不要になったらリスナーを解除してください。
- クライアント側でフィルタリングするのではなく、複合クエリを使用してサーバー側でデータをフィルタリングしてください。
- 大規模なコレクションでは、クエリを使用して監視するデータを絞り込んでください。大規模なコレクション全体を監視しないでください。
8. セキュリティ
- Firestore のデータを保護するには、必ずFirebase Security Rulesを使用してください。
- セキュリティルールは、ワイルドカードを使用しない限りカスケードされません。
- クエリの結果に、ユーザーがアクセス権を持たないデータが含まれる可能性がある場合、クエリ全体が失敗します。
ユーザー所有のドキュメントに対するルールの例:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
- インジェクション攻撃を防ぐため、Firestore に送信する前にユーザー入力を検証してください。
- 複数のドキュメントをアトミックに更新する必要がある操作には、トランザクションを使用してください。
- すべての Firestore 操作で適切なエラー処理を実装してください。
- 適切なアクセス制御なしに、機密情報を Firestore に保存しないでください。
参考資料
コメント
Markdown 対応