Cookpad Summer Internship 2021 (10 day Tech コース)を開催します!

f:id:fufufukakaka:20210426121451j:plain

研究開発部の深澤 (@fukkaa1225) です。今年になってエンジニアの立場から新卒採用を担当しています。

クックパッドでは、毎年恒例のサマーインターンシップを今年も開催します!本記事では、エンジニアコースについてご紹介いたします。

以下のインターンシップ特別サイトからご応募いただけます。

日程と講義内容について

エンジニアコースは、今年は 10 日間の日程で行います。

前半 5 日間は講義・ハンズオン形式です。 クックパッドのアプリケーション開発技術や、サービス開発のノウハウについて学びます。 開発技術として、モダンなフロントエンド化のために採用した React、TypeScript、Next.js、クックパッドを長く支えているRuby on Rails、AWS などを題材に講義を実施します。 サービス開発講義では、クックパッドにおけるユーザーの課題解決のための考え方をインプットし、それを体感していきます。

後半 5 日間は実践的なプログラムとして、OJT プログラムと PBL プログラムの2種類を実施します。 OJT プログラムではクックパッドの部署に配属され、メンターの指導のもとサービス開発を実践します。 PBL プログラムでは社員エンジニアのサポートのもと、身近な課題を解決するサービスを自ら提案し実装していきます。

今年は以下の日程で開催されます。

  • 8/16(月) 〜 8/27(金)

昨年の様子については、以下の記事をご覧ください。

開催形式について

前半の講義はオンラインで行われますが、後半の実践プログラムでは OJT プログラムをオンサイト、PBL プログラムをオンライン、オンサイトのいずれかを選択できる形式で実施することを予定しています。

クックパッドオフィスは 2021 年 5 月より、恵比寿からみなとみらいに移転します。今回のインターンは移転後初のサマーインターンということもあり、参加される方々が新しいオフィスの雰囲気、そこで働く社員の人たちとの交流をできる限り体感できるようにしたいと考えました。 COVID-19 の感染拡大状況を注視しつつ、オンライン、オンサイト両方の準備を現在進行中です。


参加してくださる皆さまのため、サマーインターンシップには毎年会社を挙げて取り組んでいます。 クックパッドのエンジニアになった「未来の自分」を体験できた、と参加して頂く皆様に実感してもらえることを目指して準備を進めていきます。

また、長期の就業型インターンシップも通年で募集しています。 興味のある方は、以下のページからご応募ください。

皆さまのご応募をお待ちしています!

KomercoとFirebaseの話【後編】 - Firebase運用の仕組化

Komercoの高橋です。

昨日は前編でFirestoreの設計パターンについてお話しましたが、後編はFirebase運用の仕組化についてです。

前の記事でも述べたように、昨年はWeb版のリリースや送料無料イベントもあり、ユーザ数がさらに増加してきています。 サービス規模が大きくなるにつれて運用コストも大きくなり、その効率化も求められるようになってきました。

ここではKomercoで行っている、運用に関する仕組みについてご紹介します。

Komercoの構成

まず前提としてKomercoの構成について簡単にご紹介します。

f:id:yosuke403:20210421170018p:plain

Komercoは器や料理道具、食材や調味料を扱うECサービスで、商品の販売者から直接購入ができるC2Cサービスとなっています。 商品を購入するユーザを「カスタマー」、販売するユーザを「クリエイター」と呼んでいます。

Komercoではこのカスタマー、クリエイターそれぞれに対してiOS版、Web版のアプリを提供しています。Web版についてはCloud FunctionsでNext.jsを動作させて実装しています。

各アプリはFirestoreやCloud Functionsにアクセスしてデータの取得や更新を行います。

Firestoreのデータやユーザの行動ログはBigQueryに貯まるようになっていて、Google スプレッドシートやTableauで分析しています。

もちろん他にもFirebaseの様々な機能を利用しています。

定期的にFirestoreのデータをBigQueryにエクスポート

Komercoでは毎日FirestoreのデータをBigQueryにエクスポートし、分析に使用しています。 Komerco発足時はエクスポートする手段がなかったので独自でツールを作っていましたが、今はgcloudを使ったエクスポート方式に移行しています。

やり方については次の通りです。

サービスアカウントの作成

ここを参考にロールを設定します。

バッチを動かす

バッチで叩くコマンドは次のようになります。

# サービスアカウントのキーファイルをセット
gcloud auth activate-service-account --key-file key.json

# プロジェクトのセット
gcloud config set project my-firebase-project

# collection groupごとにFirestore→Storageへデータをエクスポート
gcloud firestore export gs://firestore-export-for-bq/2020-06-29T10:17:23.011+09:00 --collection-ids=user,shop,product

# collection groupごとにBigQueryのテーブルに入れる
bq load --source_format=DATASTORE_BACKUP --replace=true --projection_fields=name firestore.user gs://firestore-export-for-bq/2020-06-29T10:17:23.011+09:00/all_namespaces/kind_user/all_namespaces_kind_user.export_metadata
bq load --source_format=DATASTORE_BACKUP --replace=true --projection_fields=name,owners firestore.shop gs://firestore-export-for-bq/2020-06-29T10:17:23.011+09:00/all_namespaces/kind_shop/all_namespaces_kind_shop.export_metadata
bq load --source_format=DATASTORE_BACKUP --replace=true --projection_fields=name,price firestore.product gs://firestore-export-for-bq/2020-06-29T10:17:23.011+09:00/all_namespaces/kind_product/all_namespaces_kind_product.export_metadata

projection_fields オプションがあるおかげで、個人情報など取り込みたくない情報はフィルタ可能です。

Dockerfileを使う場合は、gcloudを使うためのGoogle公式イメージがあるので、これを利用するのが便利です。

今からサービス公開するならFirebase Extension

もし今の時点でサービスが未公開の場合はFirebase Extensionを使う選択肢があるかもしれません。

firebase.google.com

こちらの場合、すでにFirestoreに入っているデータについてはエクスポートされないため、今回はgcloudの手法を選択しています。

AuthのデータをGoogle スプレッドシートにエクスポート

ユーザにメールを送りたいときなどに、Authからメールアドレスを抽出する必要があります。 個人の環境でデータを取得するのは情報の取り扱い上問題があるため、バッチでGoogle スプレッドシートに直接書き込むようにしています。

Google スプレッドシートに書き込むことのメリットとして、アクセス管理が簡単なことや、リストのフィルタなどがその場でできることが挙げられます。

サンプルはこちらで、このプロジェクトをバッチで実行します。 https://github.com/yosan/FirebaseAuth2Sheet

ロールバック

Cloud Functionsにはロールバックの仕組みがないため、デプロイ後に不具合が発覚した場合にはもう一度過去のバージョンをデプロイする必要があります。 Web版のKomercoもNext.jsをCloud Functionsで実行している関係上、Hostingのロールバックだけでは意味がありません。

Komercoの本番環境にデプロイする際は、個人の環境からはできず、クックパッドで利用されているRundeck+hakoの仕組みを使ってデプロイしています。 masterブランチに変更が入ると、JenkinsでCloud FunctionsやWebアプリをビルドし、それが入ったDockerイメージをAmazon ECRにプッシュします。 デプロイ時はRundeckからhakoを使うことで、プッシュされたDockerイメージを取得して firebase deploy コマンドを実行します。 Rundeckは基本的にruboty経由で呼ぶため、Slackと上で ruboty deploy komerco-cloud-functions のように発言して指示します。

f:id:yosuke403:20210420173149p:plain

ECRにプッシュされたDockerイメージにはタグがついていて、hakoではこのタグをオプションで指定することで過去のビルド成果物を使ってデプロイができます。 ロールバック時は前回デプロイした際のDockerイメージのタグを特定して、再度デプロイします。

f:id:yosuke403:20210420173528p:plain

Cloud FunctionsのCI環境改善

サービスの機能が増えるに連れてCloud Functionsの数は増えていき、Komercoでは100を超えるCloud Functionが動いています。 Firebaseの機能拡張が進み、その運用コストも下がってきました。

オフラインテスト

Komercoリリース時点ではFirebaseに関するテスト環境はまだまだ充実していなかったため、CIは実際のFirestoreを使ってオンラインテストを行っていました。 しかしこれにはテスト用のプロジェクトが必要だったり、CIの度にデプロイが必要だったりといろいろ不便な点がありました。 現在ではCIのテストは全てオフラインに切り替わっており、CIにかかる時間は大幅に短縮されています。

テストを実行する際は、firebase emulators:exec コマンドから、FirestoreやCloud Functionsのエミュレータを起動させています。 起動中はAdmin SDKの書き込みは自動的にエミュレータのFirestoreの方を向くため、オンラインテストの実装をほぼそのままオフラインに移行できました。

https://firebase.google.com/docs/functions/local-emulator#interactions_with_other_services

Firebase Admin SDK を使用して Cloud Firestore に書き込む Cloud Functions がある場合、Cloud Firestore エミュレータが実行されていれば、この書き込みはエミュレータに送信されます。この書き込みによってさらに Cloud Functions がトリガーされると、それらは Cloud Functions エミュレータ内で実行されます。

ここで、もともとオンラインで行っていたテストには、

  • 単体のCloud Functionsに対して行うテスト
  • カスタマーが商品を購入するまでの一連のFirestoreへの操作を再現してテストするシナリオテスト

の2種類がありました。

Cloud FunctionsとFirestoreエミュレータが同時に起動している場合、Firestoreへの書き込み時にFirestore Event TriggerのCloud Functionsがエミュレータ上で発火するようになります。 シナリオテストはこのFirestore Event Triggerの動作も含めて、仕様通りに更新が行われるかをチェックします。 一方で単体のCloud FunctionsのテストにおいてはFirestore Event Triggerは不要で、発火してしまうとテストにかかる時間が伸びてしまいます。

そこで、シナリオテストのみ他のテストとフォルダ単位で分離してテストを別々に実行しています。 シナリオテストの場合のみCloud Functionsエミュレータを起動し、Firestore Event Triggerを発火させてテストします。

Cloud Functions名の定義ファイル

KomercoではCloud Function名やその実体のパス、グループなどが書かれた定義ファイルをfunctionDefinitions.ts として用意しています。

この理由は「Cloud Functions実行時のモジュールの動的ロード」と「同時デプロイ数を制限したフルデプロイ」において、このファイルをimportして使用するためです。

定義ファイルの例です。

type FunctionProperty = {
  name: string
  module: string
  implementation: string
}

type FunctionDefinition =
  | ({ type: 'single' } & FunctionProperty)
  | { type: 'group'; groupName: string; functions: FunctionProperty[] }

export const functionDefinitions: FunctionDefinition[] = [
  {
    type: 'single',
    name: 'createShop',
    module: './shop',
    implementation: 'onCreateShopCalled',
  },
  {
    type: 'group',
    groupName: 'product',
    functions: [
      {
        name: 'create',
        module: './product',
        implementation: 'onProductCreated',
      },
      {
        name: 'update',
        module: './product',
        implementation: 'onProductUpdated',
      },
    ],
  },
]

export default functionDefinitions

type はグルーピングされたCloud Functionsか単独かを表します。 name は関数名、 moduleimplementation が実装先を表します。

Cloud Functions実行時のモジュールの動的ロード

Cloud Functionsは実行時に必要なモジュールのみロードするようにしないと、初回のコールドスタート時の実行時間に影響が出ます。 そこでKomercoでは実行されたCloud Functionsによって、動的にロードするモジュールを切り替える仕組みを入れています。 定義ファイルからCloud Function名と実装先を読み取ってロードします。

import functionDefinitions from './functionDefinitions'

const shouldExport = (functionName: string): boolean => {
  const currentFunctionName = process.env.K_SERVICE
  return (
    currentFunctionName === undefined || currentFunctionName === functionName
  )
}

functionDefinitions.forEach((definition) => {
  switch (definition.type) {
    case 'single':
      if (shouldExport(definition.name)) {
        exports[definition.name] = require(definition.module)[
          definition.implementation
        ]
      }
      break

    case 'group': {
      const groupedFuncs = definition.functions.reduce((previous, current) => {
        const functionName = [definition.groupName, current.name].join('-')
        return shouldExport(functionName)
          ? {
              ...previous,
              [current.name]: require(current.module)[current.implementation],
            }
          : previous
      }, {})

      exports[definition.groupName] = groupedFuncs
      break
    }
  }
})
フルデプロイ時の書き込み制限問題

サービスが拡充されるに連れてCloud Functionsの数は増え、現在では100ほどのCloud Functionsが作成されています。 Komercoでは本番にデプロイする際は、基本的にmasterブランチにあるFirebaseプロジェクトの内容をすべてデプロイします。 ここで、Cloud Functionsが多くなるにつれて、デプロイの制限にひっかかって頻繁にデプロイエラーを起こしていました。

https://firebase.google.com/docs/functions/manage-functions

多くの関数をデプロイすると、標準の割り当てを超過し、HTTP 429 または 500 エラー メッセージが表示されることがあります。これを解決するには、10 個以下のグループで関数をデプロイします。

そこで定義ファイルから全関数のリストを取得し、最大10個ずつデプロイする仕組みを作っていました。

次のようなデプロイ用スクリプトをTypescriptで実装を作成し、 ts-node で実行してしました。

import { functions } from './src/functions'

const deploy = async (option: { only?: string; except?: string }) => {  
  let command: string | undefined  
  if (option.only) {   
    command = `yarn run firebase deploy --force --only ${option.only}`   
  } else if (option.except) { 
    command = `yarn run firebase deploy --force --except ${option.except}`   
  }  

  if (command) {   
    const stdout = execSync(command)    
    console.log(stdout.toString())  
  }  
}

const main = async () => {  
  const chunkedFunctions = ... // functionsを最大10個ずつに分割

  await deploy({ except: 'functions' })    // Cloud Functions以外をデプロイ

  for (const funcs of chunkedFunctions) {    
    await deploy({ only: funcs.map(f => `functions:${f}`).join(',') }) // 分割されたfunctionsをデプロイ
  }  
}    

main().catch(e => { 
  console.error(e)  
  process.exit(1)  
})

Cloud Functionがグルーピングされているされている場合、そのグループのCloud Functionsは一度にデプロイするようにしています。 これは、デプロイにONLYオプションを使う場合に、グループ内で減ったCloud Functionがある場合に自動で削除が可能なためです。 グルーピングされていないものについては自動削除できなそうなので、これからCloud Functionが増えることを考えて初期の段階からグルーピングを使った方がいいかもしれません。

これによってデプロイエラーは無くなったのですが、デプロイに非常に時間がかかるようになってしまいました。

firebase-tools v9.9.0でデプロイのリトライが追加

実はこの記事を書いているうちfirebase-tools v9.9.0がリリースされ、先に述べたデプロイエラーが発生したときに自動的にリトライする仕組みが入りました。そのため現状は一度にデプロイする関数を10個以上に指定しています。

ただし今でも、全て一度にデプロイしようとするとエラーになることがあるようで、引き続きこの仕組が必要になりそうです。

まとめ

Komercoで導入しているFirebase運用の仕組みについてご紹介しました。 こういった仕組み化により、エンジニアは少ないながらも高速に開発ができています。 ご興味ある方は、ぜひ弊社に遊びに来てください。

info.cookpad.com

ここで告知

実は本日4/22(木)から5/5(水)まで春のオンライン陶器市を開催しています。 なんと期間中は送料無料です。 これに加えて、本日からお茶・紅茶・珈琲カテゴリが追加されました!

ぜひこの機会にお買い求めください。

komer.co

KomercoとFirebaseの話【前編】 - Firestoreの設計パターン

こんにちは。Komercoの高橋です。

Komercoがリリースされてからもうすぐ3年が経とうとしています。 クックパッドの新規事業「Komerco」ではバックエンドのほぼ全てをFirebaseで運用してします。 新規事業のためエンジニアの数はまだ少ないものの、Firebaseのおかげでエンジニアはサービス開発に専念できている状況で、昨年はWeb版のリリースや送料無料イベントもあり、ユーザ数がさらに増加してきています。

これまで多くの機能を開発してきましたが、その中でFirebaseをより有効に使えるよう試行錯誤してきました。 これからFirebaseを使おうか考えている人にも、今現在Firebaseを使っている人にも参考になるよう、Komercoで得た知見を書いていこうと思います。

前後編となっており、前半はFirestoreの設計についてお話しようと思います。

当たり前の機能だけどFirestoreだと実現しにくいもの

Komercoは器や料理道具、食材や調味料を扱うECサービスです。 そのためFirestoreには「ユーザ」「商品」「ショップ」などのコレクションが存在し、主にこれらを操作しながら買い物の機能を実現しています。 また、ユーザとショップがやり取りを行うメッセージ機能や、Komercoからのお知らせ機能など、サービスとして標準的な機能もFirestoreで実現しています。

開発を続ける中で気づいたのは、一般的なサービスでよく見る機能でも、Firestoreで実現すにはひと工夫必要なことがあることです。 こういった問題にぶつかる度に、開発メンバーでどんな設計ができそうかアイデアを出し合ってきました。

ここでは、どういった機能がFirestoreで実現しにくいのか、それに対してどんな設計パターンがあるのか、それらのメリット・デメリットは何かを書いていこうと思います。

リスト表示に複数のコレクションの情報が必要

最初はよく語られる内容ですが、クライアントサイドジョインの問題です。 あるコレクションのリストを表示したいときに、各リストアイテムごとに別コレクションのデータが必要になるケースです。

例えばKomercoでは商品のリストを表示する画面で、各セルにショップの情報も表示しています。

f:id:yosuke403:20210420151618j:plain

Firestoreでは単一のクエリで単一のコレクションしか取得できない制約上、次のいずれかの手段を取ることになります。

冗長化: 取得するコレクションに別コレクションの情報を含めるようにする。

f:id:yosuke403:20210420151722p:plain:w700

クライアントサイドジョイン: クライアント側でコレクションを別々に取得して結合する。

f:id:yosuke403:20210420151700p:plain:w380

ちなみに、最初は単一コレクションの取得で済むよう設計できていても、その後の拡張で別コレクションのデータが必要になることも多いと思います。 Komercoも最近のアップデートで、商品のセルにショップの情報も載せるようになりました。 冗長化するかクライアントサイドジョインにするかの選択は、サービスを続けている間に必ず経験することになるのではないでしょうか。

実態としてクライアントサイドジョインが多くなりがち

冗長化には次のメリットには、

  • リスト表示に必要なデータの取得が一回のアクセスで済む。
  • クライアント側の実装がシンプルになる。

などがあり、クライアントサイドジョインのメリットは、

  • Firestoreのストレージ消費が少ない。
  • 冗長化の仕組み(Cloud Functionsの実装等)が不要。

などが挙げられると思います。

Komercoはあまり冗長化の選択ができていません。というのも冗長化の仕組みの実装・メンテナンスコストが大きくなりがちだからです。 例えば先ほどの商品にショップの情報を載せる例では、冗長化を行う場合、商品のコレクションにショップの情報の一部をCloud Functionsで付与する訳ですが、

  • ショップ情報更新時にそのショップの商品すべてを更新するCloud Function
  • 商品情報の新規作成時にショップの情報を取得して付与するCloud Function
  • Firestoreセキュリティルールの確認・変更
  • Firestoreインデックスの確認・変更(大きいフィールドを除外するなど)
  • (リリース後の場合)すでにFirestoreに存在する商品に対してショップ情報を付与する作業

等々が必要になります。 うまく管理していかないと、冗長化のCloud Functionsが増え続け、どのドキュメントを更新すると何に影響が出るのか分からなる可能性があります。

クライアントサイドジョインの場合、確かにクライアント側の実装コストは増えますが、大抵の場合はセルが画面に表示される直前にデータ取得する実装をするだけです。 セルに画像を表示する際によく行う実装と一緒です。 そのため、基本的にクライアントサイドジョインの選択を行っている状況です。

冗長化がうまくいった例

Komercoで冗長化がうまくいった例としては、商品一覧画面の画像取得の高速化があり、以前ブログでも書きました。

techlife.cookpad.com

この例のように、

  • クライアントサイドジョインでは処理完了までの時間が遅い
  • 対象の処理速度はサービスにとって重要である

といった場合において、冗長化の仕組みの導入コストに見合うのではないかと思っています。

Cloud Functionsで取得するという選択肢について

Cloud Functionsでジョインしてから返すことも考えたことがありますが、個人的にはあまりよくない手法だと思っています。 理由はFirestoreのセキュリティルールを無視した取得が可能になってしまうからです。

セキュティルールによって誰がクライアントを実装しても想定外の更新はなされないという安心感があり、特に新しいメンバーがジョインしたときになどに有効です。

どうしてもCloud Functionsで実装したい場合はそのようなケースに気をつけつつ、Firestoreと同じリージョンで作成するのがよさそうです。

リスト表示にページャーをつける

f:id:yosuke403:20210420153231p:plain

Firestoreで特定のコレクションのデータを一覧表示していく場合、一番簡単なのはアプリ上で一番下までスクロールしたら続く内容を追加ロードする仕様です。 これはFirestoreのstartAfterなどのカーソル句を使ったクエリと相性がいいためです。

一方で困るのは特定のページを指定して、その範囲のリストを取得することです。 特にPCの場合、追加ロードよりもページャーでリストを見ていく方が一般的かと思います。 ページを指定するということはつまり、取得開始位置と取得個数をクエリに含められばよいのですが、取得開始位置の指定はクライアントライブラリにはありません。

KomercoのWeb版はPCでの利用も想定しており、実現方法についてよく議論しました。

ドキュメントに何番目かを表す番号を付与する

クライアント的に理想的な状態は、何番目かを示すフィールドがドキュメントに追加されることだと思います。 取得範囲を指定が簡単で、全部で何件あるかも最後のドキュメントを取得すれば分かります。

ただこれは一方で、ドキュメントの順序変更や削除が発生した場合に、影響するコレクションの番号を振り直しをCloud Functionsで行う必要があります。 頻繁に更新されるようなコレクションの場合は、書き込み数の上限などに注意が必要です。

f:id:yosuke403:20210420153434p:plain:h400

ページ指定以外の方法を考えてみる

例えば週に1回定期的に更新される記事コンテンツのようなものの場合、ページではなく年月を指定してその範囲の記事を取得する方法が考えられます。 これであればFirestoreのクエリが書けるので、実現は容易です。

f:id:yosuke403:20210420153455p:plain:h400

全件取得

先に挙げた戦略が取れない場合、データ量的に問題ないことを前提に、全件取得の判断をすることもあります。 これは最後の手段なので、仕様を調整するなど、できるだけ別の解は無いか考えるようにしています。

未読にバッジをつける

サービスからのお知らせやメッセージ機能を実装すると必要になるのが未読機能です。 未読の保存方法についてはいくつかパターンがあります。

未読管理対象のドキュメントに保存する

未読管理対象のコレクションに対して、

  • 閲覧するユーザ数が限定されている
  • 各ユーザの未読・既読状況が、他の閲覧可能ユーザに公開されてよい

のであれば、そのコレクション自体に未読・既読を表す情報を入れてしまうのがよいと思います。 未読の有無の確認もクエリひとつで確認できますし、クライアントサイドジョインも不要です。

Komercoのメッセージ機能を例に説明します。 この機能は、クリエイターとカスタマーの1対1チャットを行うものです。 チャットールムに当たるRoomコレクションがあり、そのサブコレクションとして各チャットメッセージに当たるMessageコレクションがあります。

例えばクリエイターがメッセージを送信すると、Messageコレクションにドキュメントが追加されます。 Cloud Functionはそのイベントを受けて、親のRoomのドキュメントを更新し、カスタマーの未読有を示す isCustomerRead フラグを false にします。 カスタマーは isCustomerRead == false で絞り込むことで未読メッセージを見つけられますし、Messageコレクションを取得しなくてもそのチャットルームに未読があることが分かります。

f:id:yosuke403:20210420154207p:plain

既読管理用のサブコレクションを作る

「サービスからのお知らせ」のように、全体向けの情報に対して未読管理したい場合、先の例のようにお知らせのドキュメントに全ユーザの未読情報を書き込むのは現実的ではありません。

そこで例えば、ユーザのサブコレクションに既読管理用コレクションを作成し、お知らせを読んだ場合はそのお知らせのIDを持つドキュメントを既読管理用コレクションに追加します。 お知らせ表示時はそのコレクションを比較し、既読管理用コレクションに存在しなければ未読と判断されます。

f:id:yosuke403:20210420154225p:plain

ただし、「1つでも既読のお知らせがあればバッジを表示したい」といったときに、お知らせも既読管理用コレクションも、最悪全件取得しないと分からないという問題があります。

最後に閲覧した日付より前の記事を未読とする

例えばお知らせで、お知らせごとに個別に未読管理する必要がない場合は、最後にお知らせ一覧を開いた日付を覚えておき、それ以前のお知らせを既読としてしまう方法があります。 この場合、未読の有無はその日付を使ってクエリすることができます。

f:id:yosuke403:20210420154315p:plain

ドキュメントごとの既読管理の重要性が低い場合は、こちらの手法がよいと思います。

まとめ

Komercoの経験をベースに、Firestoreを使った開発で多くの人が悩みそうなケースについて説明しました。 どの設計を選ぶかは結局仕様次第になるので、仕様をよく分析して設計を選んでみてください。

弊社もまだまだ試行錯誤中なので「自分はこうしています!」というアイデアをお持ちの方、ぜひお話聞かせてください。

info.cookpad.com

後編は、Firebaseに関する仕組化を行って、運用コストを下げている話をしようと思います。