2020/8/23 firebase:data model 訳

Understand Cloud Firestore>>Data model

Cloud Firestore Data model

Cloud FirestoreはNoSQLのドキュメント志向のデータベースです。SQLデータベースとは違い、テーブルや行はありません。代わりにデータはドキュメントに格納します。ドキュメントはコレクションの中に整理されます。

それぞれのドキュメントにはキーバリューペアのセットが含まれます。Cloud Firestoreは、小さなドキュメントを多数格納することに最適化されています。

全てのドキュメントはコレクションの中に格納されます。ドキュメント内に「サブコレクション」を格納することができます。ドキュメント内にネストしたオブジェクトを格納することもできます。サブコレクション・ネストしたオブジェクトはStringなどの基本的な型のフィールド、あるいはリスト(配列)のような複雑なオブジェクトを含むことができます。

Documents

Cloud Firestoreではstorageの単位はドキュメントです。ドキュメントは軽量の記録です。ドキュメントは値と、値に関連づけられたフィールド名を持ちます(つまりキーバリューペア)。各ドキュメントはname(名前)で識別されます。

ユーザーalovelaceを表すドキュメントは以下のようになります。

📃 alovelace

first : "Ada"
last : "Lovelace"
born : 1815

Note

Cloud Firestoreは様々な型のデータをサポートしています。boolean,number,string,geo point(位置情報),binary blob,timestamp。ドキュメント内でデータを構成するために配列や(Mapなどの)ネストしたオブジェクトを使うこともできます。

ドキュメント内の複雑な、ネストしたオブジェクトはマップ(maps)と呼ばれます。例えば、マップを使って、前の例のユーザーの名前を構成するとこのようになります。

📄alovelace(←ドキュメント)

name :
first : "Ada"
last : "Lovelace"
born : 1815

ドキュメントはJSONに似ていると気づかれる方もいると思います。実際似ていますね。JSONとドキュメントはいくつか違う点があります(例えばドキュメントはJSONがサポートしていないいくつかの特別な型をサポートしています。またドキュメントのデータサイズは1MB以下と制限されています。)しかし一般的に、軽量のJSONレコードをドキュメントとして扱うことができます。

Collection

コレクションの中に複数のドキュメントが入ります。コレクションは複数のドキュメントをいれるコンテナ(入れ物)です。例えば、usersコレクションの中に、いろいろなユーザーの情報をドキュメントとして格納することができます。

📁users(←コレクション)

📄alovelace(←ドキュメント)

first : "Ada"
last : "Lovelace"
born : 1815

📄aturing(←ドキュメント)

first : "Alan"
last : "Turing"
born : 1912

📂users(←コレクション)
    📄alovelace(←ドキュメント)
    first:"Ada"
    last:"lovelace"
    born:1815
    📄aturing(←ドキュメント)
    first:"Alan"
    last:"Turing"
    born:1912

 

Cloud Firestoreはスキーマレス(schemaless)です。ですので、ドキュメントの中にどんなフィールドを用意するか、またそのフィールドにどんな型のデータを格納するかも自由です。コレクションの中の各ドキュメントがそれぞれ違うフィールドを持つことも自由ですし、各フィールドが違う型のデータを格納することも自由です。

しかし一つのコレクションの中で、全てのドキュメントが同じフィールド・同じデータ型を持つようにすると、より容易にドキュメントをクエリ(問い合わせ)することができます。

コレクションはドキュメントのみを持つことができます。コレクションがキーバリューペアを直接持つことはできません。(キーバリューペアが格納されたドキュメントを持つことはできる。)。コレクションが直接、別のコレクションを持つこともできません。

コレクションの中の各ドキュメントの名前はユニーク(一意)である必要があります。(一つのコレクションの中に同じ名前のドキュメントがあるのはダメ、ということ。)。ドキュメントにあなたが生成したuserIDSのようなkeys(キー)を提供することもできますし、Cloud FirestoreにランダムなIDを自動的に生成させることもできます。

あなたがコレクションを「生成」したり、「削除」したりする操作を行う必要はありません。コレクションの中に一番最初のドキュメントを生成すると、自動的にコレクションが存在します。コレクションの中の全てのドキュメントを削除すると、自動的にコレクションも削除され存在しなくなります。

References

Cloud Firestoreの全てのドキュメントはデータベース内の場所により一意に(ユニークに)識別されます。前の例で、usersコレクションの中の「alovelace」ドキュメントが登場しました。この「alovelace」ドキュメントの場所をプログラムのコードから参照するには、ドキュメントへの参照(reference)を生成(取得)する必要があります。

CollectionReference cr2=Firestore.instance.collection('users');
var dr1=cr1.document('alovelace');
//usersコレクション内にalovelaceドキュメントが存在しない状態で、
//これだけ実行してもalovelaceドキュメントは生成されない。
//firebaseコンソール上では何も起こらない。

referenceは軽量のオブジェクトであり、データベースのなかの場所を指し示します。その場所にデータが存在していてもしていなくてもreference(DocumentReference)を生成することはできます。referenceを生成するだけでは全く何のネットワーク処理も行われません。

CollectionReference cr2=Firestore.instance.collection('users');
var dr2=cr2.document('aturing');
dr2.setData({'first':'Alan','last':'Turing','born':1912});
//ここまで実行すればusersコレクション内にaturingドキュメントが生成される。
//取得したaturingドキュメントのリファレンスにデータをセットするのがsetDataメソッド

コレクションのリファレンスを生成することもできます。

CollectionReference cr2=Firestore.instance.collection('users');

Note:

Collection references(コレクションリファレンス)とdocument reference(ドキュメントリファレンス)は異なるリファレンスの型です。例えば、コレクション内のドキュメントを問い合わせ(検索)するためにコレクションリファレンスを使いますし、個別のドキュメントのデータを読み書きするためにドキュメントリファレンスを使います。

便利な機能として、ドキュメントやコレクションへのpathを文字列でスラッシュ(/)区切りで指定する方法でリファレンスを生成することもできます。例えば、Johnドキュメントへの参照を生成するのには

var dr3=Firestore.instance.document('users/John');
dr3.setData({'first':'John','last':'Doe','born':1888});

Hierarchical Data

Cloud Firestoreで階層構造のデータがどのように機能するかを理解するために、チャットアプリについて考えていきます。

📁rooms

📄roomA

name:”my chat room”

📄roomB

チャットルームの集合がroomsコレクション、一つ一つのチャットルームがroomAドキュメント、roomBドキュメント、という感じです。

どのようにメッセージ(messages)を格納していくか考えます。メッセージを各チャットルームドキュメントに格納したくない、と考えるかもしれません。Cloud Firestoreのドキュメントは軽量である必要があります。チャットルームドキュメントには数多くのメッセージが格納される可能性があります。

そういう場合、各チャットルームドキュメントの中にコレクションを追加することができます。ドキュメント内に追加するコレクションのことを「サブコレクション」と言います。


Subcollections

このケースでメッセージを格納するベストプラクティスはサブコレクションを使用することです。サブコレクションは特定のドキュメントに関連づれられたコレクションです。

Note:

Collection Group Queriesを使用することで、サブコレクション内を同一のcollectionIDで問い合わせ(検索)できます。

messagesという名のサブコレクションを、roomsコレクションの中の各チャットルームドキュメントの中に作成することができます。

📁rooms(←コレクション)

📄roomA(←ドキュメント)

name:”my chat room”

📁messages(←サブコレクション)

📄message1(←サブコレクション内のドキュメント)

from:”alex”

msg:”Hello World!”

📄message2(←サブコレクション内のドキュメント)

📄roomB

📂rooms(←コレクション)
    📄roomA(←ドキュメント)
    name:"my chat room"
           📂messages(←サブコレクション)
           📄message1(←サブコレクション内のドキュメント)
                   from:"alex"
                   msg:"Hello World!"
                📄message2(←サブコレクション内のドキュメント)
                ...
       📄roomB(←ドキュメント)
       ...

 

この例では、以下のようにしてサブコレクション内のメッセージドキュメントへのリファレンスを生成(取得)することができます。

    var messageDR=Firestore.instance.collection("rooms").document("roomA")
        .collection("messages").document("message1");
    messageDR.setData({'from':'Ben','msg':'How do you do?'});

上記のようにcollectionとdocumentは交互に登場する必要があります。このパターンでないといけません。コレクションの中のコレクションを参照することはできません。ドキュメントの中のドキュメントを参照することはできません。

サブコレクションを使うことで、データを階層的に構築することができますし、データへのアクセスが容易になります。roomAドキュメント内の全てのメッセージの取得は、messagesサブコレクションへのコレクションリファレンスを生成(取得)し、他のコレクションへアクセスする方法と同様の方法で可能です。

サブコレクション内のドキュメントがサブコレクションを持つことも可能です。これによりデータをネストすることができます。100階層のネストが可能です。

Warning:ドキュメントを削除してもそのドキュメント内のサブコレクションは削除されません!

例えばcoll/docが削除されて存在しなくても、coll/doc/subcoll/subdocは存在する可能性があります。ドキュメントを削除したときにその中のサブコレクション(のドキュメント)も削除したい場合、手動で削除する必要があります。

main.dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  CollectionReference cr1=Firestore.instance.collection('baby');
  CollectionReference cr2=Firestore.instance.collection('users');
  //var docs1=cl1.getDocuments();

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
    /*
    //(1)documentにデータを書き込む。
    //コレクションリファレンス取得→addメソッドで書き込み。
    //var docs1=cr1.getDocuments();
    var doc2=cr1.add({'name':'shoya','votes':_counter,'team':'fcTokyo'});

    var doc2=cr2.add({'first':'Alan','last':'Turing','born':1815});

     */



    //(2)ドキュメントIDによりドキュメントを指定してデータを書き込み。
/*
    var dr1=cr1.document('doc6');
    print("${dr1.documentID}🐒🐒🐕🐥");
    //dr1.setData({'name':'Keisuke','votes':88,'team':'Nagoya'});
    dr1.setData({'name':'Shinji Ogawa','age':666});
*/
/*
    var dr2=cr2.document('aturing');
    dr2.setData({'first':'Alan','last':'Turing','born':1912});

 */
/*
    var dr3=Firestore.instance.document('users/John');
    dr3.setData({'first':'John','last':'Doe','born':1888});

 */

    var messageDR=Firestore.instance.collection("rooms").document("roomA")
        .collection("messages").document("message1");
    messageDR.setData({'from':'Ben','msg':'How do you do?'});


    //var drx=cr1.document('docx');




  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child:FutureBuilder(
          future:cr1.getDocuments(),
          builder: (context,snapshot){
            if (!snapshot.hasData) return LinearProgressIndicator();
            return Center(
              child: Column(
                children: [
    //snapshot    ==>   AsyncSnapshot<QuerySnapshot>型
    //snapshot.data   ==>   QuerySnapshot型
    //snapshot.data.documents   ==>   List<DocumentSnapshot>型
    //snapshot.data.documents[0]    ==>   DocumentsSnapshot型
    //snapshot.data.documents[0].data   ==>   Map<String, dynamic>型
                  Text("${snapshot.data.documents[1].data['name']}"),
                  Text("${snapshot.data.documents[1].data['votes']}"),
                  Text("${snapshot.data.documents[3].data['name']}"),
                  Text("${snapshot.data.documents[3].data['votes']}"),
                ],
              ),
            );
            },



        /*
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
         */




      ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

参考

https://firebase.google.com/docs/firestore/data-model?authuser=0

コメントを残す

メールアドレスが公開されることはありません。