知り合いからの依頼で作って運用した、
小規模イベント用の予約システムを
設計メモだけでも残して供養します。
要望
- イベント内でブースは複数且つ席数上限あり、参加者はあらかじめ**「ユーザー登録」し「見たいブースの予約」**をする
- 公開は基本的にLPページのみ。(ユーザーページ? 作ってもいいよ!)
- 予約は「フォーム送信で仮登録」「ユーザー動作で登録有効化」と段階を踏むこと
- 一定時間は仮予約状態でもブースの席を押さえられるように!
- 仮予約のフォームはLPに埋め込むこと。また、「入力・確認・修正・送信」できるように!
ユーザーシナリオ
- ユーザーがLPページにアクセスし、フォームから「ユーザー登録」「イベント予約」を実行
- バックエンド側で仮登録処理→本登録用リンクをメール送信
- ユーザー側で本登録用リンクをクリック(ページにアクセス)
- ページが開かれた際にバックエンドにユーザー有効化をリクエスト
- 本登録完了
使用したサービス
- Firebase …… ホスティング、運営側の認証で利用。小規模サービスで便利すぎる。
- Nuxtjs …… ページ作成・処理で利用。
- Google Apps Script …… doGet、doPostを用いたNuxtjsのバックエンド処理。
- スプレッドシート …… DBの代わり。また、一部クエリ処理的なものをスプレッドシート関数で代用。
- googleフォーム …… スプレッドシートに新規登録する際の排他処理代わり兼データバックアップ
※当初バックエンドはLaravelで作ることも考えたが、規模が大きくなるのが嫌だったので上記の構成に。
それに、**スプレッドシートでデータも見えるから、エクセルにコピペもできて便利でしょ?**とか考えてた。
どう作っていったか
前後するので、各サービスの役割だけ。
Firebase
hosting機能はほぼそのまま使った。
せいぜいhostingの設定で、firebase.jsonの「trailingSlash」をfalseにしてURL末尾のスラッシュを失くしたこと、
headersでキャッシュの設定をしたことくらいか。
Authenticationは運営側のみの利用のため今回は割愛。
google apps script
色んなサービス連携で便利。
おおまかには以下の内容で処理を行い、データを返す形とした。
また、仮登録・本登録時は「gmailapp」を用いて
本登録用リンク記載メール(仮登録有効時間内アクセスで本登録化)、
本登録完了メールを送信した。
- doGet
- 予約状況のjson
- doPost
- ユーザー重複登録チェックのvalidate(OK/ErrorMessage)
- 予約可否のvalidate(OK/ErrorMessage)
- ユーザー、ユーザーブース予約の仮登録(true/false)
- 本登録(true/false)
スプレッドシート
googleフォームの保存先をひとつのスプレッドシートにまとめること、
また、VLOOKUPかCOUTIFS等を用いることで
データの集約と、大幅な処理コードの削減ができる。
今回以下のシートを準備した。
- 席数上限(定数)、仮登録の有効時間の計算用タイマーなどを書いた「設定シート」
- ユーザー情報、自動作成する識別のための固有ID、登録時の時間、登録時の時間と上記仮登録タイマーを照合する「有効な仮登録」、本予約状態の可否を書いた「ユーザーシート」
- ユーザーIDと予約ブースの紐づけ。&そのユーザーが有効(仮登録有効時間内 or 有効化済み)かどうかを書いた「予約シート」
- (仮登録有効時間内 or 有効化済みの)ユーザーの予約から算出する「予約状況シート」
googleフォーム
スプレッドシートへの情報入力時、gasを使って入力処理を行うが、
単純に「最終行+1に新規データを追加」とした場合、
タイミングによっては上書きされる形で処理が終わってしまう。
防ぐためには、登録中は他者の処理にストップをかける
「排他処理」が必要だが、gasのロックでは
複数台でいっせーのーせっでやるとタイムラグがモロにでる……。
代替案で使ったのがgoogleフォーム。
あらかじめスプレッドシートの内容に合致する形でフォームを作成。
フォームの項目のID、また、フォームのpost先URLを調べ、
google apps scriptのfetchを用いて情報をpostする。
フォームの保存先を「既存のスプレッドシート」とし、
上記のスプレッドを選択したらOK。
(現状は)処理もれや同一行登録を防ぐことができた。
Nuxtjs
LP(兼仮予約)ページでは
参加ブース指定、ユーザー登録という2つのフォームの
「入力・確認・修正・送信」をまとめるために、
以下の4つのコンポーネントを作成、
データ入力の進捗、操作によってコンポーネントを切り替えた。
- 参加ブース指定
- ユーザー登録のための情報入力
- 確認(修正ボタンあり。操作で1or2に戻る)
- 送信
また、本予約確定ページでは、アクセスした段階で本予約確定とするため、
mounted時にバックエンドにURLのパラメータをポストする処理を書いた。
(あとはバックエンド側で照合、該当ユーザーを有効化)
※余談だけど、gasとの連携で大分躓いた。
axiosとgasが絶妙にマッチしない……というより使い方に合致してないのか。
ちょっと詰まったと思ったら、やり方ごそっと切り替えるのオススメです。
axiosで書いていたのをfetch apiにしたり
「heroku cors」とか検索してみたりすると幸運があるかもです。
評価
スピード重視しようと色んなサービス使った結果、
変に複雑化&処理の低速なサービスになってしまった。
小規模サービスなら、もっとシンプルに、もしくは定型のものを使うべきだった。
ちょっとした推しだった**「データの可視化」「エクセルとの互換」も
「え、自分でやるの?」と不評**だったので
今度からはこのやり方は採用しません。
(美味しいコーヒー奢ってもらったから変な顔はしないけど)