CloudFunctionsでフォロワー、フォローの数を集計する #CloudFunctions #CloudFirestore

シェアする

フクシャチョーです。

今日はCloudFunctionsでフォロワー・フォローのカウントの集計をします。

よくある、あの数字の表示をするものです

アプリで、Aさんのフォローボタンを押すと、

自分のFollowingのカウントと、AさんのFollowerカウントが+1になります。

逆に、Aさんのフォロー解除ボタンを押すと、

自分のFollowingのカウントと、AさんののFollowerカウントが−1になります。

これをCloudFunctionsで集計します。

アプリ側の実装

データベースはCloudFirestoreを使います。

アプリからフォロー、フォロー解除のアクションを起こした時に、
relationshipsコレクションにドキュメントを追加、削除するようにします。

ドキュメントのIDは「フォローする側のID_フォローされる側のID(followerId_followedId)」とします。

CloudFunctions側の実装

CloudFunctionsでは、relationshipsコレクションに、ドキュメントが追加、削除されたイベントで、userコレクションの、対象ユーザーのドキュメントのfollowerCountfollowingCountを更新します。

イベントタイプ

今回はrelationshipsコレクションに、ドキュメントが追加、削除されたのをトリガーとしたいので、onWriteを実装します

Cloud Firestoreのイベントタイプとトリガーについて

Cloud Firestore は、createupdatedeletewrite イベントをサポートしています。

イベントタイプ トリガー
onCreate ドキュメントが最初に書き込まれたときにトリガーされます。
onUpdate すでに存在するドキュメントの値が変更されたときにトリガーされます。
onDelete データを含むドキュメントが削除されたときにトリガーされます。
onWrite onCreateonUpdate または onDelete がトリガーされたときにトリガーされます。

イベントデータ

さて、ここからイベントに含まれるデータを取得するのですが、今回利用しているSDKはv1.0のAPIとなります。
v1.0からイベントデータが DataSnapshot に変更されています。
以前のリリースでは、event.data は DeltaSnapshot でしたが、現在の v 1.0 では DataSnapshot です。

onWrite イベントと onUpdate イベントのデータ パラメータには before フィールドと after フィールドがあります。これらはそれぞれ DataSnapshot です。

変更前後のデータを取得するのは以下のように記述します

exports.dbWrite = functions.firestore.document('/path').onWrite((change, context) => {
  const beforeData = change.before.data(); // data before the write
  const afterData = change.after.data(); // data after the write
});

さて、実際にrelationshipsコレクションのCloudFunctionsを実装してみましょう。

引数で受け取るchangeは以下のようなデータが入って来ます。

Change {
  before: 
   QueryDocumentSnapshot {
     _ref: DocumentReference { _firestore: [Object], _referencePath: [Object] },
     _fieldsProto: 
      { createdAt: [Object],
        followedId: [Object],
        followerId: [Object] },
     _readTime: undefined,
     _createTime: '2018-05-13T06:56:27.184847000Z',
     _updateTime: '2018-05-13T06:56:29.831391000Z' },
  after: 
   DocumentSnapshot {
     _ref: DocumentReference { _firestore: [Object], _referencePath: [Object] },
     _fieldsProto: undefined,
     _readTime: undefined,
     _createTime: undefined,
     _updateTime: undefined } }

beforeにデータがあり、afterにデータがない場合は削除

beforeにデータがなく、afterにデータがある場合は登録

となります。

その為、CloudFunctions内では、変更前後のデータがない場合は無効とし、

変更前のデータが存在する場合は、カウントを-1

変更後のデータが存在する場合は、カウントを+1としています。

export const updateFollowerCounts = functions.firestore
  .document("relationships/{relationshipId}")
  .onWrite((change, context) => {
    if (change.after.data() && change.before.data()) {
      return null;
    }
    let countChange;
    if( change.before.data()){
      countChange = -1
    }
    if( change.after.data()){
      countChange = 1
    }

次にcontextのパラメータから、relationshipIdを取得します。

contextには以下のようなデータが入って来ます

{ eventId: '070b6d17-f88c-4d5a-babe-0b93db6d95a9-0',
  timestamp: '2018-05-13T06:56:29.831391Z',
  eventType: 'google.firestore.document.write',
  resource: 
   { service: 'firestore.googleapis.com',
     name: 'projects/fir-dev-15eca/databases/(default)/documents/relationships/AAsPEJFotxeEI4nnXud5g7n2OOF3_wnufjmOvkdctnfQQy5ckx2EKSrr2' },
  params: { relationshipId: 'AAsPEJFotxeEI4nnXud5g7n2OOF3_wnufjmOvkdctnfQQy5ckx2EKSrr2' } }

relationshipIdは「フォローする側のID_フォローされる側のID(followerId_followedId)」となっているのでsplitで分割します。

    // Get the user ids
    const ids = context.params.relationshipId.split("_");

    const followerId = ids[0];
    const followedId = ids[1];

CloudFirestoreのトランザクション処理

さてここからがCloudFirestoreのトランザクション処理に入ります。

フォローする側のユーザードキュメントとフォローされる側のユーザードキュメントを同時に更新する必要があるためです。

トランザクションと一括書き込み  |  Firebase 

// Reference the document locations
    const db = admin.firestore();

    const followerRef = db.collection("users").doc(followerId);
    const followedRef = db.collection("users").doc(followedId);

    // Return a transaction promise
    return db.runTransaction(async t => {
      // Fetch the data from the DB
      const follower = await t.get(followerRef);
      const followed = await t.get(followedRef);

      //  format the counts
      const followerUpdate = {
        followingCount: (follower.data().followingCount || 0) + countChange
      };
      const followedUpdate = {
        followerCount: (followed.data().followerCount || 0) + countChange
      };

      // run the updates
      await t.set(followerRef, followerUpdate, { merge: true });
      await t.set(followedRef, followedUpdate, { merge: true });

      return t;
    });

アプリ側で、フォローしたり、アンフォローしてみましょう。

対象ユーザーのfollowingCountとfollowerCountが期待通りに増減すればOKです。

これで、アプリ側のユーザー画面では、userコレクションのデータを取得するだけで、以下のような数字を表示することができますね。

Firebase SDK for Cloud Functions 移行ガイド: ベータ版からバージョン 1.0 へ 

Cloud Firestore トリガー  |  Firebase 

CloudFunctions API Overview  |  Firebase 

CloudFirestore トランザクションと一括書き込み  |  Firebase