[Firestore] Field を追加せずに CollectionGroup でランダムに Document を取得する

## 前置き
Firestore の性質上、ランダム取得は少し工夫が必要です。
[StackOverflow "Firestore: How to get random documents in a collection"](https://stackoverflow.com/questions/46798981/firestore-how-to-get-random-documents-in-a-collection) で Google の人が回答しているようにいくつかの方法がありますが、[CollectionGroupQuery](https://firebase.google.com/docs/firestore/query-data/queries#collection-group-query) への言及がなかったので書きます。

## 結論
基本的に無理。
特殊な条件を満たせば可能だが、それは CollectionGroup を使う価値が無い場面。

## 具体例をもとに順を追って検証
例として Flutter の [cloud_firestore](https://pub.dev/packages/cloud_firestore) を使います。
データ構造は、トップに `User`Collection、その SubCollecion に `Tweet` を置いているとします。
この時、「`User` を限定せず、`Tweet` の中からランダムに１つ document を取得したい」場面を考えます。
これは一筋縄ではいきません。

### それっぽいのを書いてみる
と、こうなるかと思いますが、これはダメです。

```dart
// 比較パラメータとして使うためだけに document ID を生成
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;

// それを使って検索
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
    .collectionGroup('user')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
    .limit(1);
// 後略
```

`__name__` の実体は document ID ではなく、完全に一意な Path です。[^1]
そのため、こうやって怒られます。 [^2]

```terminal
Invalid query.
When querying a collection group and ordering by document ID,
you must pass a value that results in a valid document path,
but 'jM5rRdSflGyfk8Qy9Puu' is not because it contains an odd number of segments.
```

つまり、以下のように完全に一意な path を指定すれば動くわけです。実際期待した動きをします。

```diff
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;

Firestore.instance
    .collectionGroup('user')
    .orderBy('__name__')
-   .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
+   .where('__name__', isGreaterThanOrEqualTo: 'user/XXX/tweet/${dummyTweetDocumentId}')
    .limit(1);
```

しかし、これじゃあダメですね....
なぜなら、この条件を指定できるということは、client が User の document ID `XXX` を知っていることを意味するからです。
`User を限定せず` を達成できていないということです。

### だから、階層分 Query を発行する
しかないと思います。あくまで、_Field を追加しないことにこだわるなら_ の話をしています。
今回だと2~4回です。

```dart
// まず User をランダムに取得する

final dummyUserDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
final userQuerySnapshot = await Firestore.instance
    .collection('user')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyUserDocumentId)
    .limit(1)
    .getDocuments();
final user = userQuerySnapshot.documents.first;


// そして、Tweet をランダムに取得する

final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
    .collection('user/${user.documentID}/tweet')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
    .limit(1);
// 後略
```

## 結果、どうするのがベストか？
**Field を追加する**のがシンプルだなーと思いました。
ただ、そこに document ID の生成ロジックを使う場合は少しだけ悩ましいです。
どういうことかと言うと、document 自体の ID と一致させるには `add` ではなく `set` で document を追加することになるからです。
`set` には `update` の意もあるので、できれば利用は避けたいものです。
なので、**document 自体の ID とは別個に(同様のロジックを使って)値を作って入れておくのが良い**かなと考えています。そうすれば `add` で書けるので。
万が一、その Field と document の ID が一致してないといけない仕様があるなら `set` で書くしかないとは思いますが...

[^1]: ソースコードを詳かく見るとこまではまだできていないのですが、https://stackoverflow.com/a/56189687/10928938 や https://firebase.google.com/docs/firestore/security/secure-data?hl=ja#the_resource_variable からも確かかと思います。
[^2]: iOS だと https://github.com/firebase/firebase-ios-sdk/blob/330a55f9de8ab9a5cc86a5a934c68264e29b5268/Firestore/Source/API/FIRQuery.mm#L526-L531 にあたります。