この記事は Firebase アドベントカレンダー 2019 18日目の記事です。
Firebase を活用して個人開発した結婚式の写真管理Webサービスのアーキテクチャなどについて書きます!
さて前回記事になりますが、サービス自体の概要と開発の経緯についてまとめました。
今回は技術的背景の解説ということで、いきなりですが全体構成図をバーンと!
技術的には Firebase + Google Spreadsheet + Glide(というSaaS。詳細は後述) で構成しています。
以下では各要素を分類し、それぞれの役割について解説していきます。
Firebase部分
使用しているのは
- Firebase Hosting
- Cloud Functions
- Cloud Storage for Firebase
の3つです(写真送信画面での手間を極力省くため、認証は噛ませていません)
Firebase Hosting は画像送信用のページをホスティングするのに使用しています。単なる静的ホスティングとはいえ、システムの顔となる部分なのでデザインにはちょっぴりこだわりました!
画像送信用ページは新郎新婦のふたりと相談して作成。手とリボンのステキな写真はフリー素材ではなく お二人が実際に撮影した写真 ですし、てんとうむしは新婦さんのご要望です!かわいい🐞
画像のアップロード先には Cloud Storage for Firebase を使用しています。
また アップロードされたタイミングで Cloud Storage トリガー を発火 させ、以下2つの処理を行っています。
1. Create Thumbnail
写真一覧画面をサクサク表示するために、サムネイル画像を生成する処理です。
生データだとかなり高画質でそこそこのサイズなので、一覧画面を表示のために数秒を要するという激悪UXだったのでした。
この処理を使用して作成した軽量サムネイル画像を使用したことで、ほぼ遅延を感じることなく表示可能になったかと思います。細かいところまで行き届いている 💮
実装は下記、公式の function-samples
に generate-thumbnail
というドンピシャのものがあったのでそちらをほぼ丸コピしました。
2. Rotate
全体を通して一番苦労したポイントがここでした。。。全然本質的じゃないところで…
これは iPhone からアップロードした画像が横向きで保存されてしまう問題 対処のため、泣く泣く作成した処理です…。
iPhoneで撮影した写真には内部的には傾いた状態になっており、傾きはEXIFのOrientation情報として保存されます。ブラウザによってこのEXIFのOrientation情報を反映するか無視するかで対応がまちまちです。
ということなので、前述のサンプル関数とほぼ同様に ImageMagick を使用し、元画像の Orientation を自動で修正する処理を噛ませています。
該当箇所を以下に抜粋します。
await mkdirp(tempLocalDir)await file.download({ destination: tempLocalFile })console.log("The file has been downloaded to", tempLocalFile)await spawn("convert",[tempLocalFile,"-auto-orient",tempLocalRotateFile],{ capture: ["stdout", "stderr"] })console.log("Rotated File created at", tempLocalRotateFile)await bucket.upload(tempLocalRotateFile, {destination: rotateFilePath,metadata: metadata})
ちなみに Cloud Functions 実行環境のメモリはデフォルト 256MB ですが、スマホ撮影並みの画質だとこれでは全然足りずにエラーとなっていました。
そのため Rotate 関数用の実行環境は 1GB まで増強して対応 しています。下記赤枠部からGCPのコンソールを表示し、関数の実行環境を編集することが可能です。
Google Spreadsheet 部分
さて Firebase での処理はおしまい、次は Spreadsheet への転記です。
送信画像をレコードする時には Cloud Functions を使用し、アップロードされた画像の情報や、どちら側のゲストか・新郎新婦との関係性を Google Spreadsheet に記録しています。
この際、Firebase から G Suite への越境問題 が発生します。
権限まわりで一工夫する必要があるので、以前投稿した下記の記事をご覧ください。
(コチラの記事はこのサービス紹介のための布石だったのです!)
新郎用と新婦用のふたつのシートを用意し、以下の appendSheetRow
関数で本人との関係性と画像のURLを1行ごとに記録していきます。
req.body.guest
には 新郎 or 新婦 いずれかの情報が入っており、該当のシートに追記されていくという寸法です。
(シートを分けているのは後述する Glide 画面下部のタブと、スプレッドの各シートが 1対1 で対応するためです)
const range = `${req.body.guest}!A1`function appendSheetRow(jwt, spreadsheetId, range, row) {const sheets = google.sheets({ version: "v4" })sheets.spreadsheets.values.append({spreadsheetId: spreadsheetId,range: range,auth: jwt,valueInputOption: "USER_ENTERED",resource: { values: [row] }},(err, result) => {if (err) {throw err} else {console.log("Updated sheet: " + result.data.updates.updatedRange)}})}
Glide 部分
最後に PWA による写真の表示部分です。
これは Glide という SaaS を使用し、音速で作成しました。
実際に使ってみればわかりますが、本当に音速で PWA が作成できます!!
ちょっと凝った使い方をしようと思うと痒いところに手が届かないのが残念。ですが、ただ画像を表示する程度のアプリケーションであればかなりコスパ良く使えるなぁという印象です。
というわけで全体像の説明は以上です!
Firebase を軸に バックエンドのコードを1行も書くことなく(わりとそれっぽい)Webサービスを作ることが出来てしまいました 🎉
おわりに
結婚披露パーティでは、画像送信用フォームを開くためのQRコードを会場に貼っておいてもらい、出席者の方にそれを読み取って画像を送信いただきました。
ちょっと余談ですが、個人開発でなにかを作ると「本当に誰かがこれを必要としてるんだろうか…?」という疑問がついてまわります。
ですが今回の場合はセミオーダーメイドで、実際に使ってもらって喜んでもらえるところまで目にすることができたので、個人開発者冥利に尽きる案件でした(´;ω;`)うれしい。
さてこうして完成した HAPPY SEEDING には実際に数百枚(!)もの画像が集まりました。しかもそれを生データで保存しているため、数GBのストレージを使用しています。が、ほぼ Firebase 無料枠の範囲で収まっています!Googleさまさまです、ほんとに。
今後も以下のように、よりブラッシュアップをしたうえで友人の挙式の際には使ってもらえるようなものにしていきたいと思っています!
- 時系列順に自動で整理し、挙式・披露宴・二次会で画像を自動分類
- Firebase MLKit を使用し、集合写真や新郎新婦の笑顔の写真だけを抜粋
- それらを組み合わせてショートムービーを自動生成!
etc, etc... 追加したい機能やサービス自体の成長の余地はまだまだたっくさんあります!わくわく!!
使ってみたい・紹介してみたいという方がいらっしゃったら、ぜひお気軽にお声がけください!
あなたのためにお作りします 💖
おわりのおわりに
と、いうわけで!個人開発をするうえで Firebase ファミリーはやはりめっちゃくちゃ強力な武器になりますね!
引き続きみなさんの活用方法も参考に、楽しく読ませていただきたいと思います!