Firebase入門 フリマアプリを作りながら、認証・Firestore・Cloud Functionsの使い方を学ぼう!

Firebaseでは、バックエンドやインフラに精通したメンバーがいなくても、モバイルやWebフロントの開発に集中できます。Authentication、Firestore、Cloud Functions、さらにセキュリティルールまで、クックパッドの岸本卓(@_sgr_ksmt)さんが、実践的に解説します。

Firebase入門 フリマアプリを作りながら、認証・Firestore・Cloud Functionsの使い方を学ぼう!

Firebaseをご存じでしょうか? Firebaseを利用したことはありますか?
今回は「Firebaseをこれから使ってみたい!」「絶賛使っているけど、初めてでどう開発したらいいかよく分からない……」という方を対象に、実践的な入門という形で説明していきます。

Firebaseとは?

Firebaseは、2011年にFirebase社がサービスを開始し、2014年にGoogleが買収したMBaaS(Mobile Backend as a Service)です。

Firebaseでは、リアルタイムでデータを同期できるCloud FirestoreやRealtime Databaseといったデータベース、プッシュ通知を簡単に実装できるFirebase Cloud Messaging、サーバーレスに何かのイベントをトリガーに関数を実行するCloud Functions for Firebaseといった機能を利用できます。

その他にも、次のようなたくさんの機能があります。

Authentication、Hosting、Cloud Storage、Crashlytics、Performance Monitoring、Test Lab、Analytics、Predictions、A/B Testing、Remote Config、Dynamic Links、App Indexing、In App Messaging、ML Kit

1プロダクト - Firebase

サービスが開始された当初にはまだなかった機能やβ版だった機能も多かったのですが、今ではほとんどの機能がGA(Generally Available)となっており、Firebaseが使われるシーンも増えてきたように思えます。

Firebaseの良いところ

Firebaseの良いところは、自分自身にバックエンドやインフラの知識がなかったり、精通したメンバーがいなかったりする中でサービスを開発することになっても、Firebaseがさまざまな機能をあらかじめ提供してくれているので、サーバーの構築やインスタンスの立ち上げなどを気にすることなく、モバイルやWebフロントの開発に集中できることです。

自分でいちからサーバーを立ち上げたり、認証基盤を作成したり、プッシュ通知を配信する仕組みを作る手間と時間を短縮できることは、とても大きいと思います。

実際に筆者が携わっているプロダクトでは、Firebaseを使って、iOSエンジニアのみでサービスの開発からローンチまで行うことができました(詳しくは次の記事を参照)

また、Firebase自体の開発も盛んで、機能改善や新機能が次々と搭載されるため、Firebaseで実現できる開発の幅がどんどん広がっています。 今携わっているプロダクトでも、開発当初では機能上の制約や、そもそも提供されておらず実現できなかった機能が、今では当たり前のように実装できるようになった、ということも少なくありません。

さらに、Firebaseの利用を始めること自体は無料で、有料プランでも従量課金制のほか、毎月25ドルの定額プランも用意されています。

どういう人に向いているか?

Firebaseには、さまざまな利用シーンが考えられます。

個人開発をしている人

「これから何かアプリケーションを開発したい!」という人には、筆者は強くFirebaseの利用をオススメします。 作成するアプリケーションの特性にもよりますが、以下の恩恵を受けることができます。

  • 自分でサーバーを立てる必要がない
  • 認証機能が用意されている
  • プッシュ通知を簡単に送ることができる
  • 最初からHTTPS通信に対応されたWebサービスをホスティングできる
  • インフラの知識がなくても扱うことができる
  • 提供されているモジュールやSDKを利用することで、APIを書かずにデータベースにアクセスが可能

個人開発をする人にとって、手間とコストを大幅に削減できるのはとても大きいと思います。

そして、Firebaseは無料で利用できる機能の範囲が広く、また従量課金プランに変更したとしても、そこまで料金がかかりません(規模によります)

新規事業を立ち上げたい人・企業

一昔前のFirebaseでは、MVP(Minimum Viable Product)や検証目的で作るには適している一方で、プロダクション品質まで高めるのは難しいと言われることもありました。

しかし、ほとんどの機能がGAとなり、サービスの安定性も高くなったこともあり、採用しない手はないだろうと思います。

既存事業をグロース・改善させていきたい人

既存事業でも、Firebaseを導入することで受けられる恩恵があります。

途中からでは、Cloud Firestoreといったデータベースを導入することは難しいでしょうが、AnalyticsやPredictionsによる分析や、Cloud Messagingを用いたプッシュ通知の配信など、効果を発揮する機能があります。

また、Googleが買収したFabricのCrashlyticsがFirebaseに統合されたり、Performance Monitoringといった機能が用意されたりしたことで、クラッシュしている箇所やパフォーマンスに問題がある部分を特定し、改善していく手助けになります。

Firebaseを使った開発で重要な機能

Firebaseを使った開発で特に重要となる3つの機能について、簡単に紹介します。 このAuthentication、Cloud Firestore、Cloud Functionsを組み合わせるだけでも、簡単にサービスを構築することが可能です(本記事の後半で紹介します)

Authentication

Firebaseには認証を行う機能が備わっており、認証自体を自分で実装しなくて済みます。

認証方法としては、メールアドレス等を一切必要としない匿名認証から、メールアドレスでの認証、各種サービス(TwitterやFacebook)を使った認証などがあります。

特に、次のCloud Firestoreなどを使うには、認証済みユーザーであるかどうかを検証することがベースとなるため、Authenticationの使用が必須となるでしょう。

Cloud Firestore

Cloud Firestore(以下、Firestore)は、ドキュメント指向のNoSQLデータベースです。

Firestoreを理解する上で必要不可欠なのが、「コレクション」と「ドキュメント」です。 この関係についてはFirebaseの公式ガイドと図を参考にしてください。

2

Cloud Firestore  |  Firebase

また、リアルタイム性も兼ね備えており、ドキュメントに変更があった場合、クライアント側でリッスンしておくと、変更を即座に受け取ることも可能です。

なお、Realtime Databaseで特定のノードを取得する場合に子ノードも全て取得するため非効率になるケースがありますが、Firestoreでドキュメントを取得する際には配下のサブコレクションまで取得してしまうことはありません。

Cloud Functions

Cloud Functionsは、JavaScriptで記述された関数を実行する機能です。

ファンクションには、Firestoreにドキュメントが書き込まれた・更新されたといったイベントを元に実行されるもの、定期実行のもの、HTTPSリクエストで実行できるものがあります。

トリガーを起点にFirestoreのドキュメントに変更を加える、Cloud Messagingを用いてプッシュ通知を送る、外部サービスのAPIを叩くといったことが可能になります(CLoud FunctionsからFirebaseサービス外への通信が発生する場合、無料プランでは実行できず、有料プランへの切り替えが必要)

最近では、GoやPythonでも関数を定義できるようになりました。

サンプルを通してFirebaseを使った開発を学ぶ

ここからはサンプルアプリケーションを通して、Firebaseを使った開発の仕方や設計を説明していきます。 認証からデータベース、Cloud Functionsにセキュリティルールといった形で、実際にサービスを公開するまでに必須となる内容を、実践的に学ぶことができます。

サンプルアプリケーションの全体は、GitHubの次のリポジトリで公開しています。

4sgr-ksmt/Shopping-Cart

サンプルアプリの概要

取り上げるサンプルは、とてもシンプルで簡素なフリーマーケット型のショッピングアプリです。

出品された商品を一覧や個別に表示でき、

56

気に入った商品をカートに入れて、購入できます。

78

利用するFirebaseの機能と開発環境

このサンプルでは、以下の機能を使用します。

  • Authentication
  • Cloud Firestore
  • Cloud Functions

筆者がiOS開発を得意としていることもあり、作成するサンプルはiOSアプリで開発言語はSwift、バックエンドはNode.jsでTypeScriptです。 使用した開発環境は、以下の通りです(できる限り執筆時点の最新バージョンを使用しています)

  • macOS: Mojave 10.14.3
  • Xcoode: 10.2.1
  • Swift: 5.0
  • TypeScript: 3.4
  • Node: 8系
  • Firebase iOS SDK: v5.20.0
  • Firebase js-sdk: admin, cloud-function, cloud-firestore

サンプルを動かすには

上記のリポジトリで公開しているサンプルを実際に動かすには、次のセクションから説明するように自身でFirebaseプロジェクトを作成することと、関連する設定ファイルが必要です。 iOSアプリ側の設定ファイルはGoogleService-Info.plistで、バックエンド側はadmin_sdk.jsonです。

詳細は、上記リポジトリのREADME.mdを参照してください。

Firebaseプロジェクトのセットアップ

ここからは実際に手を動かしながら、Firebaseを利用する方法を解説します。

まず、Firebaseのプロジェクトをセットアップしましょう。 Firebase consoleにアクセスして、「プロジェクトの追加」から新たにプロジェクトを作成します。

9

このウィンドウで、プロジェクト名、プロジェクトID、Firestoreとアナリティクスで使用されるロケーション(リージョン)を指定します。

プロジェクトIDとロケーションは後で変更できないので、注意が必要です。 また、今では東京リージョン(asia-northeast1)や大阪リージョン(asia-northeast2)を 選択できるので、国内に向けたサービスを作りたい場合は選ぶとよいでしょう。

iOSアプリ開発環境のセットアップ

iOSアプリに関しては、通常通りXcodeプロジェクトを作成したのち、FirebaseをCocoaPods経由でインストールすれば、おおむねセットアップは完了します。

今回は、Firebase iOS SDKのうちCore Auth Firestore Functionsの4つを扱うので、次のようなPodfileを記述して、pod installを実行します。

target 'Project' do
  pod 'Firebase/Core'
  pod 'Firebase/Auth'
  pod 'Firebase/Firestore'
  pod 'Firebase/Functions'
end

CocoaPodsそのものの利用について知りたい方は、次のエントリーなどが参考になります。

10【Swift】CocoaPods導入手順 - Qiita

バックエンド側のセットアップ

今回はCloud Functionsと、Firestoreのセキュリティルールをデプロイする必要があるので、バックエンド側のセットアップも行います。

まず、npmあるいはyarnで、firebase-toolsをインストールします(筆者はyarnを使います)firebase-toolsは、マシンのグローバル領域にインストールしてもよいですし、プロジェクトにインストールしてもかまいいません。

グローバルでインストールする場合は、次のようになります。

$ yarn global add firebase-tools

筆者は、複数プロジェクトそれぞれで独立してバージョン管理ができるように、プロジェクト毎にインストールするようにしています。

$ yarn init                     # package.jsonを作成(対話は全てスキップでOK)
$ yarn add firebase-tools

firebaes-toolsがインストールできたら、セットアップを実施します。 以降はプロジェクトにfirebase-toolsをインストールした場合のコマンド例です (グローバルにインストールした場合、プレフィックスのyarnは不要)

$ yarn firebase login
$ yarn firebase init

このように、まずFirebaseにログインfirebase loginしないと、セットアップfirebase initやデプロイは実行できません。

Firebaseのセットアップは対話式に進みます。 セットアップしたいプロジェクトを選択し、以下のように答えていきます。

What file should be used for Firestore rules?
そのまま(firestore.rules)
What file should be used for Firestore indexes?
そのまま(firestore.indexes.json)
What language would you like to use to write Cloud Functions?
TypeScriptを選択(JavaScriptがよい場合はJavaScriptを選択)

もしセットアップしたいプロジェクトが選択項目に現れないときは、firebase loginでログインしたアカウントに閲覧権限があるかどうか確認してください。

バックエンド側の調整と整理

ここまでが済むと、指定したディレクトリに、functionsディレクトリができ、次のファイルが含まれていると思います。

  • firebase.json
  • firestore.rules
  • firestore.indexes.json
  • tslint.json
  • tsconfig.json
  • index.ts

ひとまずこの状態でもセットアップできているのですが、

  • ディレクトリ構造を見直したい
  • デプロイ時に指定するソースコードの場所を整理したい

という理由から、さらに少し手を加えます。 具体的には、次の3つのファイルを書き換え

  • package.json
  • tsconfig.json
  • firebase.json

ディレクトリの構成を、最終的に以下のようにします。

.
├── config
│   └── admin_sdk.json
├── dist
├── firebase.json
├── firestore.indexes.json
├── firestore.rules
├── node_modules
├── package.json
├── src
│   └── index.ts
├── tsconfig.json
├── tslint.json
├── yarn-error.log
└── yarn.lock

package.jsonには、次のようにbuilddeployのコマンドを記述しておきます。

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc && cp package.json ./dist && cp config/admin_sdk.json ./dist",
    "deploy": "firebase deploy"
  },
  "engines": {
    "node" : "8"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "~7.0.0",
    "firebase-functions": "^2.3.0"
  },
  "devDependencies": {
    "firebase-tools": "^6.9.2",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
  "private": true
}

buildは、functionsのソースコードをトランスパイルした.jsファイル、package.jsonadmin_sdk.jsondistディレクトリにコピーするコマンドになっています。 deployでは、firebase.jsonで指定したdistディレクトリの内容を元にデプロイを行います。

以下は、tsconfig.jsonは次のようになります。

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "dist",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

firebase.jsonです。

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
    "source": "dist"
  }
}

ここまでで、iOSアプリとバックエンドの両方で開発を進めるセットアップが一通り完了しました。 構成に迷ってしまった場合は、サンプルプロジェクトと照らし合わせて確認してみてください。

匿名認証を有効にする

プロジェクトがセットアップできたら、Firebase consoleで「Authentication」を選び、「ログイン方法」の項目を開きます。 ここにはFirebaseで認証するためのログイン手段が並んでいます。

今回は匿名認証を使用したいので、「匿名」の項目を有効にします。 これにより、アプリ側からメールアドレス等不要で認証させることが可能になります。

11

以降、他の認証方法を使用する場合は、同様にこの画面から有効にする必要があります。 新規で認証方法を増やす場合にエラーが発生した場合には、設定を有効にしているかどうか見直しましょう。

なお、今回のサンプルでは使いませんが、TwitterやFacebookといった認証方法を使ってのログインも可能になっています。

Firestoreをセットアップする

次に、データベースをセットアップをします。 Firebaseには、リアルタイムでデータを同期できるクラウドベースのデータベースが2つありますが、今回はRealtime Databaseではなく、Firestoreの方を使用します。

Firebase consoleで「Firestore」を選択するときに、セキュリティルールがどちらかを聞かれますが、ひとまず「テストモード」を選択します。 これにより、(一時的に)全てのドキュメントへのアクセスが可能になります。

12

ロックモードを選択すると、全てのドキュメントに対する操作は行えない状態になっているので、別途セキュリティルールを記述する必要があります (セキュリティルールに関しては後述)

ここまでで、Firebaseで最低限必要な機能のセットアップが完了しました。

サンプルで扱うデータモデルの構成

今回のサンプルでFirestoreに格納するデータモデルについて説明します。

登場するドキュメントは、User(ユーザー)Product(商品)CartItem(カート)Order(注文情報)の4つで、構成は以下のようになっています。

ドキュメント 場所 管理するコレクション
Userドキュメント データベースのルート usersコレクション
Productドキュメント データベースのルート productsコレクション
CartItemドキュメント Userドキュメントの配下 cart_itemsサブコレクション
Orderドキュメント Userドキュメントの配下 ordersサブコレクション

また、Productドキュメントにはそのドキュメントの作成者Userのドキュメントのパス(reference)を、CartItemドキュメントにはカートに入れた商品Productのドキュメントのパスを持たせるようにしています。

なお、今回はサンプルのため簡略化していますが、本格的にサービスを運用する場合は、次のような設計の追加が必要になるでしょう。

  • Userドキュメントにプロフィール写真のURLを持たせる
  • 注文時に必要な住所を表すAddressドキュメントを、Userドキュメントのサブコレクションとして持つようにする
  • Productドキュメントに、公開・非公開を表すbool値を持たせる(例えば、isPublished
  • Orderドキュメントに、注文ステータスや配送ステータスといった情報を持たせる
  • 売上を管理するドキュメントを設計する

決済に関わる部分の情報(カード情報等)は、適切にセキュリティルールで保護した上でFirestoreに持つのもよいですし、自社の決済基盤やStripeを活用するのもよいでしょう。

〈補足〉FirestoreとRDBとの違いや設計上の注意点

モデルの設計にあたっては、Firestoreのようなドキュメント指向のNoSQLとRDB(リレーショナルデータベース)との違いや、それぞれでできること・できないことを意識して設計する必要があります。

RDBでは、データを正規化し、JOINGROUP BYといったSQLの操作で目的のデータを結合して取得することが定石となっています。 それに対して、Firestoreではデータを冗長化しておいたほうがよかったりします。

Firestoreのクエリにも絞り込みの機能が備わっているものの、RDBと違って結合や集計の操作を行うことが現状できないため、なるべく簡単なクエリ操作でデータを取得できる設計にしておくとよいでしょう。

また、結合などの操作が行えないため、「Aドキュメントを取得した後に、関係のあるBドキュメントをあらためて取得する」といった操作が発生するケースがあります(クライアントサイドジョインと呼ばれたりします)。 いわゆる「N+1問題」のような状況が発生するため、規模が大きくなるほど問題化することも考えられます。

クエリを活用し、適切な範囲を取得して読み込むようにロジックを調整すれば、大きな問題にはならないと筆者は思っていますが、読み込み回数の増加を少しでも防ぐため、あらかじめ「AのドキュメントにBのドキュメントの情報を書いて、冗長化する」といったデータ操作をすることもあります。

具体的な例を挙げましょう。次のように参照を持たせる方法では、productドキュメントの一覧を取得した後に、それぞれのownerの参照を元にユーザーの情報を取得してUIに反映させるというように、N+1回の読み込みが必要になります。

{
  product: {
    price: 100,
    name: 'Banana',
    owner: '/users/xxxxxx'
  }
}

対して、次のように冗長化したデータを持たせた場合には、productドキュメントを取得した時点でユーザーの情報があるので、そのままUIに反映することができ、productの一覧を読み込むだけで済みます。

{
  product: {
    price: 100,
    name: 'Banana',
    owner: {
      name: 'su-',
      profile_image: 'https://xxx.com/images/profile.png'
    }
  }
}

ただし、冗長化するデータの大元であるユーザーのドキュメントに更新があった場合は、冗長化した部分も更新する必要があり、読み込みは減るものの、書き込みは必然的に増えますし、更新を忘れるとデータの整合性が保てなくなります。

このような「冗長化したデータ構造を作る」ため、Firebaseにはバッチによる一括書き込み、トランザクションを用いた書き込み、Cloud FunctionsのFirestoreイベントトリガーといった機能が備わっています。 これらを活用することで、データの整合性を担保しつつ、データを冗長化して持たせることが可能になります。

一方で、全てのデータを冗長化して持つべきかといえばそうではなく、データの読み込みの頻度、冗長化される元となるドキュメントの更新頻度によって、適切に判断する必要があります。 また、冗長化して配置する箇所が増えれば増えるほど、冗長化して配置・更新するための裏側の処理が複雑になるので、かえって設計が大変になってしまうこともあります。

今までRDBを使ってきた人にとって、同じようなデータが冗長的に配置されることは、初めのうち違和感があると思います。 その違和感も、慣れてくるとなくなり、冗長化すべきかそうでないかも見極められるようになってくると思います。

Firestoreで実現できるモデルの構成に関しては、次の記事やスライドがとても参考になります。

14Cloud Firestoreを実践投入するにあたって考えたこと#CloudFirestore データベース設計

15Firestore Database Design by 1amageek

ユーザーをAuthenticationで認証してFirestoreで作成する

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く