DIVX テックブログ

catch-img

Firestoreのリアルタイム機能を実装しながら、リスナーの仕組みを理解する


目次[非表示]

  1. 1.はじめに
  2. 2.そもそもFirebaseとは?
  3. 3.Firestoreとは?
    1. 3.1.主な特徴
      1. 3.1.1.目的に適った方法でデータのクエリと構造化
      2. 3.1.2.オンラインまたはオフラインでデバイス間のデータを同期
  4. 4.リアルタイム機能とは?
  5. 5.実際に作ってみよう
    1. 5.1.Firestore側の設定を行う
      1. 5.1.1.Firebaseプロジェクトの作成
      2. 5.1.2.Firesroreをプロビジョニングする
      3. 5.1.3.実際にデータを入れてみる
      4. 5.1.4.アプリの作成
    2. 5.2.フロントファイルの作成を行う
      1. 5.2.1.firestoreへのデータ登録
      2. 5.2.2.※APIキーについての補足
      3. 5.2.3.※Firestoreへの保存を確認する
      4. 5.2.4.リアルタイムリスナーの記述追加
  6. 6.簡単すぎるが故のもどかしさ
  7. 7.リスナーについて理解をする
    1. 7.1.初回読み込み時
    2. 7.2.新しい投稿があった時
  8. 8.ネットワークを確認してみる
    1. 8.1.初回読み込み時
    2. 8.2.新しい投稿があった時
  9. 9.コードを見てみる
  10. 10.まとめ
  11. 11.終わりに
  12. 12.採用情報
  13. 13.参考文献

はじめに

こんにちは。株式会社divxのエンジニア倉沢です。

皆さんは普段、SNSを利用していますか?

SNSが誕生してから20数年が経つ現代において、仕事やプライベート、その双方において、利用したことがない方の方が少ないかもしれません。

昨今、様々な機能開発をする中で、Firebaseの中のFirestoreの機能を使い、SNSのように、画面を更新しなくてもでメッセージのやりとりできるような機能を実装する機会がありました。

これが驚くほど簡単に実装することができる為、簡単に皆様に紹介したいと思うと同時に、実際にどのような仕組みになっているのか?どういう通信方法をしているのか?が気になりはじめた為、今回こちらにてまとめていこうと思います。

そもそもFirebaseとは?

Firebase は Google のモバイル開発プラットフォームです。WebアプリやAndroid、iOSのアプリなどの中から呼び出して利用することができます。

これにより、自らサーバーを用意することなく、バックグラウンド処理をクラウドで処理することができるサービスです。

ここ数年、BaaS(Backend as a Service)と呼ばれるクラウドサービスが普及してますが、その代表格でもあるのが、Googleの提供するFirebaseです。

Firestoreとは?

Firestoreは、FIrebaseが提供するサービスの一つである、グローバル アプリ用に構築された NoSQL データベースです。前身のRealtimeDatabaseの弱点であったデータモデルを改善したり、クエリを強化したり、大規模なアプリケーションでも使いやすくなっています。

主な特徴

目的に適った方法でデータのクエリと構造化

コレクションとドキュメントを使用してデータを簡単に構造化できます。
関連データを格納するための階層を構築し、表現力の高いクエリを使用して必要なデータを簡単に取得できます。

オンラインまたはオフラインでデバイス間のデータを同期

Cloud Firestore を使用すると、アプリデータをデバイス間で自動的に同期できます。

データが変更されると通知が送信されるため、コラボレーション エクスペリエンスやリアルタイム アプリを簡単に構築できます。

今回、ピックアップするものは、こちらの特徴を活かしたものになります。

リアルタイム機能とは?

Firestoreで、データの変更があった時に変更差分をクライアント側に通知させることができます。

複数のクライアントが同一のデータを参照し、その最新の状態を取得し続ける形にすることで、ユーザー間のリアルタイムなデータ共有が可能になります。

FirestoreのonSnapshotメソッドを使用して、それを実現することが可能です。

実際に作ってみよう

御託はおいておき、実際にどのくらい簡単に実装できるのか、実装して試してみましょう。

実際にJavascriptを使用して作っていきますが、今回は、リアルタイムリスナー機能に焦点を当てていく為、細かい記述方法などは別途公式HPを見ていただきながら参考にしていただければと思います。

Firestore側の設定を行う

Firebaseプロジェクトの作成

まずは、FIrebaseのプロジェクトを作成していきます。

Firebaseプロジェクトの作成


プロジェクト名を指定して続行を押します。

プロジェクト名の下に記載されているものは、自動で割り振られる一意のプロジェクトIDになります。

Firebaseプロジェクトの作成


その後、「このプロジェクトで Google アナリティクスを有効にする」かどうか、聞かれますが、 今回は使用しない為、無効のままで問題ございません。

プロジェクトを作成しましょう。

Firebaseプロジェクトの作成


あっという間にプロジェクトが出来上がりました。

Firebaseプロジェクトの作成


Firesroreをプロビジョニングする

今作成したFirebaseプロジェクトにFirestoreのサービスを登録していきます。

Firebaseプロジェクトの作成


FIrestoreは、セキュリティ面でも安全に使用できる設計になっておりますが、今回はテストモードとして、安易にアクセスできるように設定します。

Firebaseプロジェクトの作成


ロケーションについては、データが格納される拠点になります。

日本人向けに作成するアプリであれば、asia-northeastで問題ないでしょう。

「有効にする」を押すと、Cloud Firestoreのプロビジョニングがはじまり、データベースの利用が可能になります。

Firebaseプロジェクトの作成


実際にデータを入れてみる

今回は、チャットのコメントをイメージして作成するので、commentsというまとまり(コレクション)に、データ(ドキュメント)を作成し、そのカラムのようなもの(フィールド)にコメントを追加してみました。

Firebaseプロジェクトの作成


「保存」をすると、作成したデータがデータベースに登録されます。

Firebaseプロジェクトの作成


一旦、ここまでで、Firestoreからの操作は終了します。

アプリの作成

フロントファイル作成にいく前に、フロントと連携をするための設定を行います。

プラットフォームを選択し、アプリの登録を行います。

今回はウェブアプリとして作成するので、下記ボタンより進みます。

アプリの作成


firebaseとの連携を行うために、apikeyとprojectIdを控えておきます。

アプリの作成

フロントファイルの作成を行う

firestoreへのデータ登録

作成するファイルは一つだけです。

先ほど控えたapikeyとprojectidを元に、Firestoreとやりとりをするインスタンスを作成します。

index.html

<button id="add">投稿する</button>
<div id="result"></div>
<script src="<https://www.gstatic.com/firebasejs/6.3.4/firebase-app.js>"></script>
<script src="<https://www.gstatic.com/firebasejs/6.3.4/firebase-firestore.js>"></script>
<script>
firebase.initializeApp({
apiKey: 'AIzaSyDHOC9zB-GKveTkhy4x8XWT7-ChCFEJ3To',
projectId: 'realtime-test-6c261',
});

const db = firebase.firestore();

// ボタンを押すと、データが追加される
document.querySelector('#add').addEventListener('click', function() {
db.collection('comments')
.add({
name: 'Bさん',
text: 'こんばんは',
})
});
</script>

※APIキーについての補足

今回、APIキーをコード上にそのまま記述しておりますが、実際のアプリでも記述して問題ございません。

通常のシステムであれば、APIを使用する際のAPIキーは、細心の注意を払い、外部から見ることができないように環境変数に設定したり、データを別途管理したりすることが基本となっております。なぜなら、APIキーによって、アクセスできるユーザーを制限することができる為です。

FirestoreのAPIキーは、一般的なAPIキーとは性質が異なり、この認証機能を持たない為、特段キーを隠す必要はありません。

アプリのデータを保護するためには、別途Firebaseのセキュリティルール等の設定を行います。

※Firestoreへの保存を確認する

ブラウザでファイルを開き、「投稿する」を押すと、上記で記載している’Bさん’, ‘こんばんは’の内容が、Firestoreに追加されます。

Firestoreへの保存を確認する

Firestoreへの保存を確認する

リアルタイムリスナーの記述追加

ここが本題の箇所です。

公式のHP通りに、onSnapshotメソッドを使用し、変更を検知し、即座にブラウザに反映させるようにします。

加える記述はこちらのみ。

// onSnapshotで変更を検知し、リアルタイムリスナーの実装ができる
db.collection('comments').onSnapshot(querySnapshot => {
document.querySelector('#result').innerHTML = '';

querySnapshot.forEach(doc => {
const p = document.createElement('p');
const result = document.createTextNode(
${JSON.stringify(doc.data().text)},
);
console.log(doc.data().text);
p.appendChild(result);
document.querySelector('#result').appendChild(p);
});
});

この状態で、ブラウザを左右に2つ開き動作を確認して見ましょう。

リアルタイムリスナーの記述追加


右のブラウザで「投稿する」を押すと、そのデータがFirestoreデータベースに反映され、その内容を左側のブラウザが検知して、内容が追加されます。逆も同じような原理です。

これで、Cloud Firestore でリアルタイム アップデート機能が簡単に実装できました。

チャット機能を作成するのであれば、調整が必要ですが、リアルタイム機能の実装は大幅にいじる必要がありません。

簡単すぎるが故のもどかしさ

上記の通り、とても簡単に、Firestoreのリアルタイムアップデート機能が実装できました。

SNSでよく使われている技術が、公式の情報を参考にするだけで簡単に実装できてしまいます。

顧客のニーズに迅速に応えていくためには、任せられるところは任せて、開発を進めていく方が良いかもしれません。

しかしながら、実際どのような仕組みでリアルタイム機能が実装されているか、気にならないでしょうか。少し見ていくことにしましょう。

リスナーについて理解をする

最初に複数のドキュメントを取得する際、つまりブラウザでページを開いた際に、クライアント側にリスナーを作成します。

このリスナーによって、Firestore上の変更を検知して、クライアントサーバーにデータを取得する仕組みとなっております。

初回読み込み時

初回読み込み時


新しい投稿があった時

ブラウザを更新する必要がなく、データの変更が反映されます。

新しい投稿があった時

ネットワークを確認してみる

上記コードのonSnapshot()メソッドによって、ドキュメントをリッスンして、リアルタイム機能を実現していることはわかりました。

こちらによって、初回時と更新時のリッスン内容が異なる為、何度も同じ内容をリッスンする必要がなく、コスト的にも優しい設計になっているとのことですが、実際に検証ツールで見てみましょう。

初回読み込み時

初回読み込み時

上記のように投稿が2つある時、ブラウザを読み込むと、firebaseから2つ分のデータを取得しています。

初回のデータ取得時に、単一のドキュメントの現在のコンテンツですぐにドキュメント スナップショットが作成されます。

Request Headers

Request Headers


Response

Response


新しい投稿があった時

上記、2件の投稿がある中で、さらに「投稿する」を押すと、その値の取得は、加わった分のみのデータの取得となります。

やはり、変更された分のみリッスンして取得していることがわかります。

Request Headers

Request Headers


Response

“documentChange”は、取得するドキュメントが変更になった際に、取得することができるものです。

Response



コードを見てみる

今回使用しているfirebase-js-sdk-masterのgitから、コードを確認してみます。

packages/firestore/src/api/snapshot.ts

/**

Returns an array of the documents changes since the last snapshot. If this
is the first snapshot, all documents will be in the list as 'added'
changes.
*
@param options - SnapshotListenOptions that control whether metadata-only
changes (i.e. only DocumentSnapshot.metadata changed) should trigger
snapshot events.
*/
docChanges(options: SnapshotListenOptions = {}): Array<DocumentChange<T>> {
const includeMetadataChanges = !!options.includeMetadataChanges;
if (includeMetadataChanges && this._snapshot.excludesMetadataChanges) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
'To include metadata changes with your document changes, you must ' +
'also pass { includeMetadataChanges:true } to onSnapshot().'
);
}

if (
!this._cachedChanges ||
this._cachedChangesIncludeMetadataChanges !== includeMetadataChanges
) {
this._cachedChanges = changesFromSnapshot(this, includeMetadataChanges);
this._cachedChangesIncludeMetadataChanges = includeMetadataChanges;
}

return this._cachedChanges;
}


上記を見ると、docChangesの中身には、1回目のスナップショットの取得時には、全てのデータを取得し、2回目以降はそのスナップショットにないデータ、つまり、変更が加わったデータのみ取得していることが確認できました。

まとめ

以上のことから、Firestoreのリアルタイム機能は、クライアント側では最新のFirestoreの状態を常に監視することができるようになり、複数のユーザー間やマルチデバイスで最新のデータを同期する仕組みとなっていることが理解できました。

また、必要なデータを必要な時に取得できるリスナー機能の為、時間や、金銭のコストの負荷がかからない設計となっているようです。

終わりに

Cloud Firestoreは2019年にできた、割と新しいサービスです。

このような新しいサービスを駆使しながら開発を進めていくことは、現代の顧客にとってはとても大事はことです。

しかし、このサービスができる2019年より前はどうだったか?はたまた10年前はどうだったか?
新しいものに有り難みを感じ、取り入れながらも、常に基礎的な理解もしつつ学んでいかないと、未来に向けた開発の柔軟さがなくなるかもしれません。

私自身も、引き続き基本を大事に開発を進めていこうと思います。

採用情報

divxでは一緒に働ける仲間を募集しています。
興味があるかたはぜひ採用ページを御覧ください。

  採用情報 | 株式会社divx(ディブエックス) 可能性を広げる転職を。DIVXの採用情報ページです。 株式会社divx(ディブエックス)

参考文献

サーバーレス開発プラットフォーム Firebase入門: 津耶乃, 掌田 + 配送料無料
Google Cloud documentation  |  Documentation
@firebase/firestore  |  Firebase JavaScript API reference
GitHub - firebase/firebase-js-sdk: Firebase Javascript SDK

お気軽にご相談ください


ご不明な点はお気軽に
お問い合わせください

サービス資料や
お役立ち資料はこちら

DIVXブログ

テックブログ タグ一覧

人気記事ランキング

GoTopイメージ