こんにちは。
大体iOSとかインフラ周り開発担当のもぐめっとです。
この度firebaseを使ったサービス第一弾として、「mimicha – 匿名シェアチャットアプリ」をリリースしました。
そこで、構成やプッシュ通知の送り方とか、どんな感じで作っているのかの概要を説明していこうと思います。
※本記事はFirebase Advent Calendar2018 22日目の記事です。
21日目は@flatfisherさんの、Codelabで学ぶFirebaseとその先でした。
アプリ概要
アプリの紹介をすると、mimichaはチャットする部屋を作成し、その部屋に参加することで匿名でチャットしあえるサービスです。
一度でも参加したことがある部屋に対しては参加一覧に表示されるようになれ、通知などが飛んでくるようになります。
構成
では、そんなチャットを構成するサーバ側はどんな構成になっているのかを説明していきます。
全体構成
サービスの構成を図にするとこんな感じになってます。
実質ほぼfirebaseですね!
今流行りのサーバレスアーキテクチャ的なやつです。
インフラの構築と面倒をみなくていいのでとても楽です。
mimichaはwebも展開しているのですが、webはNuxt.jsを使ってSingle Page Applicationで作っております。
firestore構成
大まかにfirestoreの構成はこんな感じになってます
- rooms サブコレクション : チャット部屋一覧
- chats サブコレクション: 実際のチャット一覧
- users サブコレクション : ユーザ一覧
- relationRooms サブコレクション : ユーザが参加している部屋一覧。
- deviceTokens サブコレクション : 通知を送る用の一覧。各部屋の通知ON・OFFなどもここで管理。
- reports サブコレクション : 通報用の一覧
この構成はCloud Functionsとも深く密接しており、精一杯考えた結果こんな形になりました。
Cloud Functionsについて
データの整理やpush通知などはcloud functionsにて実装しているのですが、どういうタイミングで使ってるかをご紹介します。
ユーザが部屋に参加した時
ユーザが部屋に参加したタイミングで、参加一覧に表示できるようにする必要があります。
↓この画面ですね。
クライアントから、users -> relationRoomsにroomのコピーを作成します。
このドキュメントが作成されたタイミングで、CloudFunctionsはこのユーザに対して通知が行えるように、usersとroomsとdeviceTokensに対してユーザの情報を追加しています。
チャットが作られた時
実際にチャットをすると、rooms -> chatsサブコレクションに、ドキュメントが作られます。
ドキュメント作成タイミングで、CloudFunctionsは部屋に参加していて通知を許可しているユーザ全員に対して、ユーザが保持している部屋情報の更新と、端末に対して通知を行います。
このへんが結構一番苦労した点なのですが、流れにするとこんな感じ。
- usersサブコレクションから部屋に参加しているユーザ一覧を取得
- roomsのドキュメント情報を取得
- users -> relationRooms のドキュメントに対して、チャットの情報を更新する
- deviceTokensサブコレクションから部屋に参加している端末一覧を取得
- 通知の設定をみながら上記端末一覧からpush通知を送る
こんなかんじでpush通知は送られてます。
特に1, 4あたりが小技を使って頑張ってとったりしているので後ほど小技を紹介します
ユーザが部屋を削除した時
サブコレクションを入れ子にしている場合、親のドキュメントが削除されても子供のサブコレクションにあるドキュメントは残ったままになっています。
cf: サブコレクション
警告: ドキュメントを削除しても、そのサブコレクションは削除されません。
サブコレクションが関連付けられているドキュメントを削除しても、そのサブコレクションは削除されません。その後もサブコレクションには、リファレンスによるアクセスが可能です。たとえば、db.collection(‘coll’).doc(‘doc’) によって参照されるドキュメントは存在しなくなったにもかかわらず、db.collection(‘coll’).doc(‘doc’).collection(‘subcoll’).doc(‘subdoc’) によって参照されるドキュメントは存在する場合があります。ドキュメントを削除するときにサブコレクション内のドキュメントも削除する場合は、コレクションを削除するで説明されているように、手動で削除する必要があります。
ということで、上記警告にあるとおり、ユーザが部屋を削除したタイミングで、その子供にあたるchatsサブコレクションも削除しないといけません。
削除の際には、こんな注意事項があります。
cf: コレクションを削除する
コレクション全体を削除する必要がある場合は、信頼できるサーバー環境からのみ実行してください。モバイル / ウェブ クライアントからコレクションを削除することも可能ですが、そのようにするとセキュリティとパフォーマンスに悪影響を与えます。
とのお達しがあったので、CloudFunctionsを使って、roomsドキュメントの削除タイミングでchatsサブコレクションを削除するようにしています。
通報の時
iOSでコミュニケーションアプリを作る場合、通報をつける必要性があります。
mimichaでは、チャット、ユーザ、部屋に対して通報を行うことができますが、通報を行ったらクライアントからはreportsサブコレクションにドキュメントを追加しています。
repotsドキュメントが追加されたタイミングで、追加された中身をみて、chatsドキュメント、usersドキュメント、roomsドキュメント、それぞれに通報のフラグを増やして制限をかけるようにしています。
また、reportsサブコレクションにまとめることによって通報の一覧をまとめることができるので、管理の柔軟さをあげております。
firestoreの小技とか
次に、firestoreを使う上での小技や工夫した点を紹介します。
入れ子になっているサブコレクション内のドキュメント有無の検索
例えば、あるユーザが「すまん」という部屋に参加した場合、users -> relationRoomsサブコレクションにドキュメントが追加されますが、「すまん」という部屋に参加しているユーザ一覧をusersサブコレクションから取得するといった検索になります。
やり方としては、usersドキュメントに、配列データとして、relationRoomsドキュメントのdocumentIdを保管しておきます。
1 2 3 4 5 6 |
relationRoomsForSearch: [ "XXXXXXXXXXXXXXXXXXXX", "AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB" ] |
この検索用のキーに対してroomsドキュメントのdocumentIdを含んでいるかどうかというのを array-contains を使って検索しています。
使い方とかは他記事に丸投げ。
cf: firestore で配列の検索・追加・削除がサポートされたので試してみた
チャットの未読機能
よくみるチャットの未読機能。
普通に数字のカウントアップでやると、「現状のデータを読み込んで」、「+1した値を更新する」といった事になり、チャットの書き込みごとに2回のfirestoreアクセスが生じてしまいます。
ということで、この機能に関してはまたしても出番の配列データを使っています。
チャットが作成されたタイミングで、未読管理用のとこにチャットのdocumentIdを挿入しています。
1 2 3 4 5 6 |
unreadChats: [ "XXXXXXXXXXXXXXXXXXXX", "AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB" ] |
挿入に関しては、先程の記事にもあった arrayUnion を使っています。
クライアント側で、チャットを読んだら、この配列のデータを空にすることで既読したということにしています。
※15日のfirebaseアドベントカレンダーでも同じことを記載されてました!
cf: チャット機能を部分的にレベルアップさせる
firebase hostingについて
冒頭でちょろっと触れましたが、web版mimichaはNuxt.jsで作っております。
これは、jsだけで動的のページが作れるので、実質静的ファイルをfirebase hostingにおくだけで実現しています。
なのでローカルで開発してhostingにデプロイするだけで簡単にアップデートができちゃいます。
また、アプリでユニバーサルリンクを実現するために、jsonファイルもこのhostingにおいてるのですが、その際に、firebase.jsonのheadersにこんな感じでheaderの設定をする必要がありました。
1 2 3 4 5 6 7 8 9 10 11 12 |
"headers": [ { "source": "apple-app-site-association", "headers": [ { "key": "Content-Type", "value": "application/json" } ] }, ... |
firebase.jsonを使えばいい感じにヘッダーの操作やリダイレクトなどもできるので、いい感じに静的サイトを動的な感じで構築できますね。
その他サービス
firebase以外にもお世話になっているサービスもご紹介します
imgur
firebase以外のサービスとしては画像管理について、imgurというサービスを使ってます。
5chも御用達しとか。。。
imgurを使うことで、今回はcloud storageは使わないような形になりました。
Cloudflare
タダで使えるCDNの代名詞、Cloudflare。
mimichaでは、webは全部Cloudflareを通してアクセスさせております。
おかげでキャッシュに乗るのであまりhostingへのアクセスはいかない特典付き。節約になります。
静的なのりで動的なサイトが作れてしまうのも、Single Page Applicationの強みですね!
無念ポイント
今回Single Page Applicationでwebは作ったのですが、OGP対応はしてないので全部同じ画像とタイトルで表示されちゃうのが悔やまれるポイントです。
部分的にcloud functionsを使うことでogp対応もできるようなので、いずれできたら対応したいなと思っています。
cf: SNS映えするWebアプリを…!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった
また、部屋名の検索とかもないので、せっかくなら Algolia などを使って全文検索的なこともしてみたいですね!
@mogmetの所感
firebaseを使うことで、まったくサーバなどを構築しないでサービスを作れちゃいました。
とっても感動です。
実質firebaseは神サービスといっても過言ではないかとおもいます!
今後もfirebaseを使っていろいろサービスをリリース予定なので楽しみにしていただければとおもいます!
なお、iosとandroidについてのお話に関しては下記をご参考ください。
cf: 匿名型チャットmimichaを作った際に使ったiOSライブラリ19選
cf: 匿名型チャットmimichaを作った際に使ったAndroidライブラリ14選
ご静読ありがとうございました。