firestoreでlikeやfollowする際のデータ構成 / firebaseとNuxtでwebサービスつくったのでそのfirebaseまわり

![ogimg_1.png](https://qiita-image-store.s3.amazonaws.com/0/12153/016a4334-1e2b-4046-f71c-79af33f83936.png)
趣味制作で、これ最高なんすよ〜といったランキングを作れるサービス、[COUCH(カウチ)](http://couch.id)をリリースしました。Nuxtとfirebaseを一通り使用しています。
先週Nuxt系の記事を書いたので、
> **Nuxt.js Advent Calendar 2018 5日目**
> [NuxtのOGP対応について / firebaseとNuxtでwebサービスつくったのでそのNuxtまわり](https://qiita.com/uriuriuriu/items/ca8661d518ff7ceb7f53) 

今回はfirebase周りの話になります。

## 参考文献
vue, nuxt, firebaseの公式docが手厚く、流行ってるのでみなさん良い記事書くしで、自分が書くことないんじゃないかと悩みます。
まあ、とりあえずは参考&勉強になった記事から。

- [Nuxt.jsとFirebaseでSPA×SSR×PWA×サーバーレスを実現する](https://inside.dmm.com/entry/2018/04/10/nuxt-firebase) ([新記事](https://inside.dmm.com/entry/2018/11/06/nuxt2-pwa-gae-se)ありますが、構成変わったので旧記事のほうで)
- [分散カウンタ | Firebase](https://firebase.google.com/docs/firestore/solutions/counters)
  - [割り当てと制限 | Firebase](https://firebase.google.com/docs/firestore/quotas)
  - 上記割り当てにある、**書き込みとトランザクション/ドキュメントへの最大書き込み速度**が ***1 秒あたり 1***と非常に少ないので、突発的な考慮が必要な項目は検討。
- [Cloud Functions による Cloud Firestore の拡張](https://firebase.google.com/docs/firestore/extend-with-functions)

## firestoreのlike, follow処理フロー
ちょうどfirebaseとNuxtの絡む内容があったので解説します。

個別記事、`articles/<ARTICLE_ID>`にloginUserがlikeしたとします。
sub collectionならまだしも、article　doc自体はauthorしか編集出来ないようにしなければ、第三者の意図的な改変が可能になってしまいます。困る。なので、

`articles/<ARTICLE_ID>/liker/<SHARD_NUM>/`: ***LOGIN_USER***がdocに`<LOGINUSER_ID>`を追加。
`articles/<ARTICLE_ID>`: ***functions***が↑の変更を検知。そのタイミングで、ここのlike総数を更新。
`users/<USER_ID>/action/likeArticle`：***LOGIN_USER***がdocに`<ARTICEL_ID>`を追加。
`users/<USER_ID>`：***LOGIN_USER***がarticleのlike総数を更新。

のような処理にすれば、like済みarticleはfront判断できるようになります。具体的には、自身の`users/<USER_ID>/action/likeArticle`をlogin時からリアルタイムで更新取得しておけば、article表示毎に、このlikeArticleのidと一致するか確認できてfront処理だけでlike済みかの表示切り替えは可能になります。
限度はありますが、3000likeくらいまでは十分そうなのではないかと。

今回likeの場合で解説しましたが、followでも同じロジックでいけます。
ちなみに分散カウンタ、shared numのロジックは↓みたいな共有method作って使い回しました。楽。

```js
function docShardCountsWrite (targetsRef, refId, docColName, shardsNum, afterCount, updateAt) {
  const ref = targetsRef.doc(refId)
  return ref.get().then(function (doc) {
    if (!doc.exists) return
    let likes = doc.data()[docColName]
    let shardCounts = mergeObj(likes, 'shardCounts', shardsNum, afterCount)
    let allCount = 0
    Object.keys(shardCounts).forEach(key => {
      allCount += parseInt(shardCounts[key])
    })
    let setItem = {}
    setItem[docColName] = {
      count: allCount,
      shards_num: makeShardsNum(allCount),
      shardCounts: shardCounts,
      update_at: updateAt
    }
    ref.set(setItem, { merge: true })
  })
}
```

## おまけ
### よかった開発サイクル
目標は毎日githubに草。

#### 初日
- Nuxt setupとfirebase組込み
- firebase Hostingで一般公開までする

#### 毎日
- 通勤中にスマホで表示確認。issueにメモる。
- 22:00過ぎくらいから簡単な改修スタート。スマホview改修から。
- 寝るまでに一度は本番deploy。designはpc viewメインでいい。
- 余力あっても当日新規開発した項目のスマホviewは完璧に仕上げない。

簡単なスマホview改修を翌日の初手にできるようにすると、PC立ち上げるハードル下がるのでよかったです。気持ち悪いので、帰ってすぐ直したくなる。

## 開発前の自分に言いたいこと

- rulesを早めに書こう。すごい簡単。updateのauthor権限確認など律儀にfrontでやってるけど消すことに。時間もったいない。
- firebase functionsのnode8系、早めに公式対応来るのでES6コンパイル環境すぐ消すことになる。タイミング悪かった。

## firebase雑感

- 今回はDBをfirestore一本にしましたが、noSQLはRDBとは別物です。構成から取得から。良い悪いでなく、向き不向き。リアルタイム取得するようなchatデータなら本番まで生き残ります。強みを持ったまま。それ以外はプロトタイピング作成としてなら早くてありかも〜な感覚。課金まわりのセキュアなデータは導入したくない。
- functionsのfirestore監視トリガー便利
- rule最強
- firestoreに別サービスの全文検索いれるならRDBで良くない？と思ってしまう。月額かかりますし。
- 一通り経験できたので、新サービスも短時間で作れるんじゃないかとわくわく