Firestoreでusersドキュメントにuidを含めるか

2 min read読了の目安(約2600字

定石

定石ではFirebase AuthenticationのUser UIDをFirestoreのドキュメントIDにして、usersコレクションに自分だけが書けるドキュメントを作成します。(一意かつドキュメントIDに使える文字列なので)

Firebase Authentication

書き込み

db.collection('users').doc(userId).set({name:'hoge'}) // userId = uid

読み込み

let userData = {}
const docRef = db.collection('users').doc(userId)
docRef.get().then((doc) => {
  if (doc.exists) {
    useData = doc.data()
  }
})

セキュリティルール

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if true;
      allow create, update: if request.auth.uid == userId;
    }
  }
}

このときuserData.uidでUser UIDを取得できるとプログラムが非常に捗ります。 特にSNS的な機能を持つサービスで他人のuidを取得するときです。(ひとのコードを読んでみるとuidを含めている場合とそうでない場合がありました)

やり方は2通りあります。

1.ドキュメント取得時にObjectに自分でuidを追加

読み込み

let userData = {}
const docRef = db.collection('users').doc(userId)
docRef.get().then((doc) => {
  if (doc.exists) {
    userData.uid = doc.id // ここ
    useData = doc.data()
  }
})

実際のFirestore上のドキュメントと対応してないので分かりにくさはありますが、1行追加するだけです。onSnapshotやwhere句によるQueryの時も同様です。

2.ドキュメント作成時にあらかじめuidを書いておく

書き込み

db.collection('users').doc(userId).set({name:'hoge', uid: userId})

この場合セキュリティルールが複雑になります
ユーザーのuidが一致した場合だけcreateできるようにし、update時にuidを含めるのを禁止(もしくはuidが一致)する必要があります。
冗長さが気になりますが、NoSQLとはそのようなものです。

セキュリティルール

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if true;
      allow create: if request.auth.uid == userId && request.auth.uid == request.resource.data.uid;
      allow update: if request.auth.uid == userId && !('uid' in request.writeFields);
    }
  }
}

単純にusersコレクションのケースではどちらの方針でもよさそうですです。

いかなる場合もuidを書いても書かなくてもいいと言えるのか?

どこかのサブコレクションにuidをドキュメントIdとしてユーザーに関するドキュメントを作るというのも定石ですが、その中からCollectionGroupで横断的に検索するときに差がでます。
ドキュメントId指定でCollectionGroup検索することは(今のところ)できないからです。

データ構造の例: roomsコレクション以下のmembersサブコレクションにuidをドキュメントIdとしてそのルームでのメンバー情報を書いておき、自分の各ルームでのメンバー情報を取得したい場合

/rooms/{roomId}/members/{userId}  //  そのルームでのユーザーの権限などを書く

ダメなコード

db.collectionGroup('members')
  .where(firebase.firestore.FieldPath.documentId(), '==', userId)  // ダメ

いいコード(ドキュメント中にuidが書いてある前提)

db.collectionGroup('members')
  .where('uid', '==', userId)

uidを書いておいて助かりました。ダメなほうは不等号は通るようですが、等号では検索できませんでした。同じことはユーザーIdに限らずドキュメントIdで横断的にサブコレクションから検索したい場合に発生します。
アジャイルな開発をする場合、これが原因で将来DB変更するようなことが起こりえます。

結論

きわめて簡単なデータ構造で済む場合を除いて、userIdのドキュメントにuidありとなしを混在させるぐらいなら一貫してuidをドキュメント中に書いておくのがいいと思いました。

ご覧いただきありがとうございました。