Edited at

FirebaseとNuxt.jsによるSPA x PWA x サーバーレスアプリケーションの開発

初めましてsatoshiです。

エンジニアとしては業務未経験のエンジニアの僕と非エンジニアであるCEOの二人三脚で開発したバックエンドオールFirebaseアプリの知見を残しておきたいと思い記事にします。

学生エンジニアが数ヶ月で一気にプロダクトを完成させる工程という観点でも参考になれば幸いです。


アプリ概要

Wilico」というヘルスケアCtoCサービスを開発しました。

Wilicoはダイエットや食事管理が必要なタイミングに合わせて、ユーザー管理栄養士さんをマッチングし、すぐにプロによる食事管理を受けることができるサービスです。

https://wilico.jp

(β版、プラン内容はテストデータです)

生きるために食べる時代から、(健康に)生きるために痩せる時代へ〜

飽食の時代と言われるようになり、好きなものを好きなだけ食べることができる現代ですが、その先に待っているのは生活習慣病です。

すなわち、健康に生きるためには、好きなものばかり食べるわけにはいかなくなってきたわけです。

しかし、ここでいう痩せるとは、摂取カロリーを減らすことではありません。

事実、厚生労働省が毎年発表している国民健康・栄養調査によると、1975年をピークにカロリーの摂取量は右肩下がりで低下。今の日本人の平均を見ると、戦後よりもカロリーが摂れていないという統計もあります。

であるのにも関わらず、生活習慣病患者は増えるばかり。

犯人は、栄養過多と栄養の偏りにあります。

太ってしまう、生活習慣病になることの原因は運動不足ではなく食品選択の結果であるということです。

そんなところに課題感を感じ、今回はこんなサービスを作ってみました。

ヘルスケア業界に一石を投じていきます。


開発時期

2019年4月中旬〜2019年6月末


開発人数

エンジニアの自分と、CEOの2人。

Firebaseを触るのは2人とも初めてで、Vueは2人とも書いたことがありました。

CEOはコーディングと簡単なJS書ける程度


開発フロー


  • モック作成(Adobe XD)、UI設計 / 3日

  • 技術選定 / 1日

  • 環境構築 / 1日


    • Docker(今はデプロイでしか使ってない)

    • Firebaseのプロジェクト作成




- Firebase勉強タイム、DB設計 / 1日

あとは書きながら学んで行った感じで、

Firebaseに関してはひたすらドキュメントと海外のエンジニアの方の記事やソースコードを読み漁っていました。

当時はご飯食べながら海外のFirebaseのYoutubeとか観てましたw

基本リモートで週2,3回ジョナサンでオール開発というブラックさでした。。笑笑


役割

自分
Firebase, Nuxt, Cloud Functions,Stripe

相方
Nuxt,モックアップ(UI設計),コーディング

基本的には機能を自分が作って、それを相方に投げてコーディングしてもらうスタンスで開発を進めていた感じです。

後半になると相方もNuxtとFirebaseに慣れてきて開発速度が上がりました。

2ヵ月半の開発期間のうち、1ヵ月半で主な機能を完成させ

最後の1ヵ月はひたすらバグ回収とブラッシュアップをする期間に。

バグ回収辛かったなあ(笑)

今回初めて本当にユーザーがいる想定で開発したので、どうしたら使いやすいかをUI・UXに関してかなり詰めたし、

普段使ってるTwitterやLINEなど、どれだけこだわり抜いてPDCAを繰り返してあのUIが完成しているんだなと実感しました。


使用技術

APIのフロー的にはこんな感じです!

app.png

フロント
Nuxt.js

バックエンド
Firebase Cloud Functions / CloudStore

Auth
Firebase Authentication

決済
Stripe Billing / Connect

CI / CD
Circle CI

Hoting
Firebase Hosting

全文検索
Algolia

その他
Docker, Zapier,Slack

バックエンドはほとんどFirebaseに頼り切っています!

本当に便利なところと不便なところがはっきりしてて、

NoSQLが活きる開発(ちょっとした個人開発など)には最強かと思いました。

開発中にFirebaseの新しいバージョンリリースでCollectionGroupというものが使えるようになったりと、今後改善されるところは多々ありそう。(何箇所かでこれ使ってます)

ただ今回のようなテーブル数がかなり多いアプリケーション開発においてはRDBの方がやりやすいと感じました。

また、CloudStoreはクエリが貧弱なので検索周りはAlgoliaというサードパーティに丸投げしています!

Nuxtについてはここ半年書き続けてきたから慣れていたが、コンポーネントのライフサイクルやstoreの管理の仕方についてはとても勉強になりました。

一回もリロードをしないで完全なるSPAを目指すことでPWAとしてモバイルで使えるようにできたことはNuxtの恩恵として大きかったです。

開発中に導入して大きなメリットを感じたのは、以下の2点をSlackに通知するようにしたこと。

・GitHubの特定のブランチにpushしたら結果を通知

・GithubのreleaseブランチにpushしたらCircle CIで自動デプロイして結果を通知

管理栄養士の方には、管理栄養士免許証写真を提出していただいていますが、僕らが目視で確認して許可するまではプランの作成ができないようになっています。

見逃しがないように、Slack APIを用いてコーチ申請がきたらSlackの特定のチャンネルに通知を飛ばすように設定しています。

スクリーンショット 2019-07-01 6.58.24.png


Webを選んだ理由

相方がエンジニアではないので、SwiftでUIを構築していくのに開発速度が落ちる

β版でリリースしてユーザーテストを繰り返してブラッシュアップして行きたかったから


Firebaseを選んだ理由

以前RailsとNuxtで開発したときにやりにくさを感じた。フロントエンドを書いてる時間が圧倒的に長く、フロント主体ならサーバーレスで開発したほうがやりやすそうと思ってたから。

単純にRails飽きたのと、新しい技術を触ることでモチベーションを維持し一気に開発を進めるため。


苦労したところ


  • firebase.auth().currentUserが取得できない!!

これは今となっては当たり前なのですがfirebaseからデータを取得してる通信の時間があるため、間に合ってないことが原因。


mounted(){
console.log(firebase.auth().currentUser) // => undifined
}

mounted(){
window.setTimeout(() => {
console.log(firebase.auth().currentUser) // => 取得できる
}, 2000)
}

ログイン情報はstoreに入れとく。


  • リロードした時にログイン状態が消えてしまう

これはvuexのstateやpropsがリロードしたら破棄される性質を持ってるいることが原因。

vuex-persitetedというプラングインを使えば、session storageとstoreをバインディングすることができます。

当初はこのやり方でやっていたが、Nuxt.jsのライフサイクルの特性を利用してpluginsの中で(pluginsはコンポーネントのマウントよりも更に前で読み込まれます)


auth-init.js

firebase.auth().onAuthStateChanged(){}


を呼び込み、actionsを叩いてstoreに入れ込む関数を作るようにしました。

これでリロード時もログイン状態を保持できるようになり、storeのstateはコンポーネントがマウントされる前に読み込まれるので二つの問題を解決できました!


  • Firebase CloudStore(NoSQL)の設計

開発初期の段階で何がベストプラクティスなのかをかなり吟味しましたが、ここは先輩方にお聞きした方が早いと思い、

PostCoffeeさん主催のイベントに参加し、Firebaseの開発では日本で一番すごいと思っている@1amageekさんにお会いして設計面を相談させていただきました!そこから自分たちなりにtry&errorを繰り返して設計していきました。アドバイス頂いた@1amageekさんと@Masataka-nさんにはとても感謝しています!

最終的にルート階層はこんな感じです

スクリーンショット 2019-07-03 4.32.21.png

あとはこれらの下にSubCollectionとしていくつかコレクションが入っています

(roomsの下にmessagesなど)


  • StripeでCtoCサブスクリプションを行う上での最適なフロー

Stirpe Billingのサブスクリプション」の概念を利用しています。

1. まずProduct(商品)を作成し、その中に複数のPlan(料金プラン)を作成

2. ユーザーがクレジットカード登録をした段階でCustomerを作成

3. 契約が押されたら先ほど登録したPlanCustomerを繋ぎ合わせることでサブスクリプションという状態が完成します

※Customerの作成にはStripe ElementというUIコンポーネントを使ってカードのtokenを作成し、axiosでcloud functionsの エンドポイントを叩く-> Stripeで顧客データを作成


  • Stripe外部銀行口座振り込み

    僕らのサービスでは、CtoCなので銀行口座振込みなどでStripe connectも併用しています。


  • タイムラインなど多くのテーブルを跨ぐページでのデータ取得→ここでプロミス地獄に陥りasync await芸人と化す


  • リレーショナルなtransactionをかけたい場面が多く、。NoSQLの難しさも実感した。



  • チャット周り



    • 非同期でチャットの未読数を更新してナビゲーションバーに表示する


    • 未読既読
      Chat.gif



まずDB的にはmessageに誰に送ったかの情報(toプロパティ)を付与し、デフォルトで未読状態(notice: false)にしときます。

コンポーネントがマウントされた時(= 個別チャットに飛んだ時に一気に既読状態(notice: true)にupdateをかけています)

同時にチャットを開いている状態も可能性としてあるので、コンポーネントを離れる時(destroyed)にも一気に既読状態(notice: true)にupdateをかけています

チャットを送るときのデータはこんな感じです。


Chat.vue


db.collection("rooms")
.doc(this.$route.params.slug)
.collection("messages")
.add({
notice: false,
to: this.room.coach.id,
from: this.$store.state.auth.currentUser.id,
content: this.message,
timestamp: Date.now()
})


反省点

設計は本当に最初にガッチリ決めてから開発に入った方がいい。

今回はNoSQLが初めてだったのと、自分自身作りながら考えるのが性に合ってるというのもあるが、開発中に何回も設計を見直した。開発中のDB設計変更の反映はただNoSQLなので良くも悪くもマイグレーションをかける必要がないのでそこまで大変でもなかった。


まとめ

・Firebaseは習得するのに敷居が低いのと、開発の初速が早い

・Firebaseは慣れたら爆速で開発ができるが、大規模なサービス開発にはまだ追いついてない感じ(NoSQLが故に)

・いかにリレーショナルデータベースが便利かを実感した

・Nuxt(Vue)は書いてて楽しい!(規約みたいなのが結構あるのでRailsと似ている気がします)

・大体困った時は公式のドキュメントをしっかり読み込めば書いてあります。この癖がついたことは良かったかも。(Githubのissueとかにヒントあったりもする)

先日Swift UIが発表されたこともあり、これをSwiftかFlutterで書き直してネイティブでのリリースを目指します。

Firebaseは本来mbass(mobile backend as a Service)ですが、WebでもJavaScript系のバックエンドとしてめちゃくちゃ相性が良いので今後に注目していきたいです!


追記

一応会社のHPもNuxtで作りました!

https://wilico.co.jp