🐘

「ぞうのあしあと」を開発しました

に公開

はじめに

この度、ファントフット株式会社様の「ぞうのあしあと」を(受託)開発しました。

https://zounoashiato.phantfoot.co.jp

ぞうのあしあと」とは、児童発達支援・放課後等デイサービスを探す保護者と福祉施設をマッチングするプラットフォームです。
従来の保護者側からの一方的な問い合わせだけでなく、施設側からお子さんの条件に合った施設が「スカウト」を送る仕組みで、より効率的なマッチングを実現しています。

保護者は会員登録後、お子さんの情報を入力するだけで、条件に合った福祉施設からスカウトが届きます。これにより、何十件も問い合わせをして断られることや、施設が多すぎて選べないといった悩みを解決し、お子さんにピッタリな施設を見つけることができます。

本記事では、このサービスの開発経緯や技術スタックについて色々と書いていきます。

技術スタック

フロントエンド:Next.js
バックエンド:TypeScript + Go
インフラ:GoogleCloud
決済:Stripe

インフラの詳細

  • データベース:Firestore
  • バックエンド周り:Cloud Run
  • 認証:Authentication
  • ストレージ:Cloud Storage
  • バッチ処理:Cloud Scheduler
  • メール:Resend

搭載機能

搭載機能は以下の通りです。比較的シンプルで、よくあるマッチングサービスとほとんど同じです。

  • 福祉施設とお子さんそれぞれの検索(Google Mapを埋め込み、地図で探せます)
  • 福祉施設によるお子さんへのスカウト
  • 双方のチャット機能
  • Stripeでのサブスクリプション(クレカ・銀行振込・コンビニ)
  • バッチ処理(日次データをスプレッドシートに書き出し)

開発経緯

このサービスを開発するにあたって、「最速でリリースし、初期投資を限界まで抑える」ことを最優先する必要がありました。(おまけに拡張性も...)

そのため全てフルマネージドサービスで固め、さらにリクエスト数で課金されるサービスのみ採用しました。DBにSQLを採用しようか悩みましたが、インスタンス稼働時間で課金されるため初期段階では不採用にし、Firestoreを採用しました。

規模が大きくなりアクセス数が増えてくるとFirestoreの方が高コストになると思うので、その際はCloud SQLに移行する予定です。

またFirebase関連の処理は全てバックエンドで動かしています。フロントでは動かしていません。
理由として以下の通りです。

  • 認証認可が非常に複雑なため、Firestore / Cloud Storageで書けるセキュリティールールでは限界がある
  • Firebase Authでカスタム認証を導入するため(メール6桁コードやパスキーなど)
  • Google Cloudの各種サービスと連携するため(Cloud KMSやPubSubなど)

これらの理由から、(本来はバックエンド開発を省けるはずの)Firebase関連の処理を全てCloud Runで動かすことにしました。それでもフルマネージドなので、バックエンド開発の手間はそこまで大きくはありませんでした。スケーリングのことはあまり気にしなくて良いので...

クラウドアーキテクチャについて

クラウド構成図は以下の通りです。

アーキテクチャ図

Cloud Runについて

Cloud Runサービスを3つ立ち上げていますが、それぞれ以下のような役割を持たせています。

  • Webホスティング 兼 CRUD処理のAPI (TypeScript)
  • 決済処理 (Go)
  • Pub/Sub サブスクライバー (Go)

Cloud Runをわざわざ3つに分けた理由は以下の通りです。

  • Next.jsを動かしているコンテナにはメモリを少し多めに割り当てたい(主要なAPIも構築しているため)
  • 決済やPubSub処理を動かしているコンテナにはそんなにメモリを割り当てたくない(割り当てる必要がない)

特に決済やPubSub関連は同時に数十リクエストを受信することが基本的にない(=インスタンス稼働数がほとんどが0か1な)ため、コールドスタート問題を差し置いてでもメモリ割り当てを減らした方がよいと判断しました。

いずれはバックエンドは全てGoに移行する予定です。

高頻度なアクセス、高速なレスポンスが要求されるようなサービスではないので、ユーザー規模が大きくなってもクラウド構成を変えることはないかなと思っています。
個人的にk8sを使ったマイクロサービス構築にも興味がありますが、どう考えてもオーバースペックなので必要ないでしょう。

Pub/Subについて

PubSub経由で動かしている機能は以下の通りです。

  • メール送信
  • バッチ処理

これは別にメインのCloud Runで動かす必要はなく、また処理に失敗した場合にリトライさせる必要があるため、PubSub経由で動かしています。

メール送信は認証コードの送信や新規メッセージ通知、スカウト通知などに使用しています。
メール送信サービスはResendを使用しています。

https://resend.com/

バッチ処理はCloud Schedulerで毎日トリガーしてPubSubにパブリッシュしています
主に日々の統計データをスプレッドシートに書き出しています。

Cloud KMSについて

アーキテクチャー図の中央の少し上にCloud KMSがありますが、これはチャットメッセージをAES-256鍵で暗号化/復号するために使用しています。
メッセージに関してはサービス運営側が閲覧していいものではないので、暗号化してFirestoreに保存する必要があると考えました。
そこで、エンドツーエンド暗号化を組み込もうと判断しました。

https://cloud.google.com/kms/docs/key-management-service?hl=ja

これを導入することで、「メッセージをやり取りしている当人たちのみが閲覧できる」という非常に安全なチャット機能を実現しています。(当たり前)
Firestore管理画面を見ても、メッセージドキュメントには謎の文字列が表示されているだけです。人力での解読はできません。
(その代わりレスポンスは...少し...うーん...という感じです...一応並列で暗号化/復号しているんですけどね...)

今後の進化

現在はあくまで「福祉施設とお子さんをマッチングする」というシンプルなサービスですが、将来的には以下のような機能を追加したいと考えています。

  • 福祉施設業務の自動化(お子さんの記録の一元化、保護者への共有)
  • 複数施設とのデータ連携

この記事を読んでいただいた方の中で、もし身近に「福祉施設探しに苦労されている方」や「お子さんを探すのに苦労している福祉施設の方」がいらっしゃいましたら、ぜひ「ぞうのあしあと」をご利用ください!

https://zounoashiato.phantfoot.co.jp

(余談ですが、Webサイトを開いた時のコールドスタート問題...ちょっと無視できないレベルですね...そのうちなんとかします...)

以上です!

Discussion