こんにちは!とある高校のSE志望の学生です。
今年の文化祭で、私のクラスは模擬店としてホットドッグを販売することになりました。
そこで、注文から受け渡しまでを担うPOSシステムを1から開発・運用してみました!
インフラには一番手軽に使えて自由度も高いCloudflareを採用しました。
予行を含めた3日間で568件・976個の注文を処理し、最終的には無事に完売。
来場者人気投票でも総合2位をいただくことができました。
本記事では、どんなシステムを作ったのか、
そして設計から運用までを通じて得た経験などをまとめてみようと思います。
(ほぼ)初投稿で、少し長くなってしまいましたが
最後まで読んでいただけると嬉しいです!
1. 背景と目的
きっかけ
例年の文化祭では、炎天下に大行列ができるという問題が食品販売にはありました。
そこで、効率よく商品を提供するため、
あらかじめ注文内容を把握できるシステム を作ろうと思ったのがきっかけです。
Webシステム開発経験者は少なく、開発は私一人で担当することになりました。
開発以外は、デザインが得意な人や飲食バイト経験者などのクラスメイトと協力しました。
イラストをクラスメイトに頼んだり、色々なフィードバックをもらったりしました。
本番3日前、雨天プログラムへの変更
当初は屋外テントで注文・会計・調理・受け渡しを全て行う「晴天版」を想定し、
完成まで漕ぎ着けていました。
しかし、文化祭直前に 雨天プログラムでの実施 が決定し、導線が一変しました。
「中庭で整理券を受け取って教室へ移動し、教室前の端末で受取申請を行い、
準備後に番号で呼び出す」 という雨天版の導線に合わせて急遽システムを再構築しました。
ある程度の共通点はあったものの状態遷移などに変更があるため、
気づかないところで旧仕様が混ざるリスクを考慮しコードベースは1から作り直しました。
2. システム概要
インフラ構成
超短期開発だったため、フロントエンドからバックエンドまでJS(TS)に統一し、
インフラにはサーバレス構成(全てCloudflare上に構築)を採用しました。
- Pages : フロントエンド静的コンテンツ(Next.js)
- R2 : TTS音源、BGMなどフロントエンド用メディアファイル
- Workers : APIサーバ
- Durable Objects : 注文データの管理、WebSocketによる全端末のリアルタイム同期
- KV : システム設定値管理
会場に設置する端末には学校配布のChromebookを利用しました。
フロントエンド設計
1. 中庭 (注文・会計)
- 注文ページ で店員が注文作成・発番・支払い処理
- 効率化のため金額決定に必要な注文個数のみを入力
- (電子決済端末と連携させたかったが、APIの個人開発者締め出しで断念……orz)
2. 教室 入口 (受取申請)
- 設置した端末(受取申請ページ)から客自身がトッピング(ケチャップ・マスタード)を選択
- 受取申請が行われた(=教室に受け取りに来たと確認できた)注文から、厨房で調理開始
3. 教室 厨房 (最終調理)
- 次に調理すべきものを 厨房ページ で確認
- 衛生上操作ができないため、受け渡し時に調理完了済み扱いになって消える
4. 教室 受け渡し窓口 (受け渡し)
- 厨房から調理済みの商品が届いたら、 受渡(スタッフ)ページ で呼び出し
- 注文番号を確認して受け渡し、注文に完了マークをつける
5. 教室 プロジェクター (呼び出し)
- 受渡(掲示)ページ を投影
- 受け渡し窓口で呼び出された番号を表示
- TTS(ずんだもん)で音声呼び出し
6. 来場者のスマホ (ゲスト用ページ)
- 注文番号を入力することで段階を確認可能
- 注文完了→受取申請済み→呼出中→受け渡し完了
- 待ち組数や呼出状況もリアルタイムで表示
他にも
- 個別注文管理ページ : 注文の状態を個別に確認・変更
- 全注文段階監視ページ : 注文の段階ごとに個数と注文番号を確認
- 販売数監視ページ : 販売数の推移を確認可能
- 設定ページ : 厨房での表示件数やオプション売り切れ設定などの要件面の設定管理
- システム管理ページ : 一括操作やバックアップ/リストア、SQL発行などの非要件面の操作
などのページを用意しました。
3. 設計思想と工夫
1. ユーザー(来場者)体験を最優先
-
直感的なトッピング選択:
ケチャップやマスタードを選ぶと、画面上のホットドッグのイラストに実際に
トッピングがかかるアニメーションを実装してみました。
作業ではなく、文化祭のコンテンツの一つとして楽しんでもらえる体験を目指しました。 -
個人のスマホでの進捗確認:
客個人のスマートフォンからも待ち組数や呼び出し状況を確認できるようにしました。
教室の混雑緩和や、教室以外で待つお客さんへの対応を意識しました。 -
音声による呼び出し:
呼び出し番号を表示すると同時に、ずんだもんの音声で読み上げを行いました。
色々なテンプレートと注文番号を分けて事前レンダリングしておくことで
単調さを避けたりもし、親しみやすい雰囲気作りを目指しました。
2. エンドユーザーだけでなくスタッフの操作負荷も最小化
-
操作が負担にならないようUIを最適化:
過剰な確認や操作手順を削減し操作数を最低限に抑えると同時に、
誤操作防止に必要な最低ラインは見極めて残しました。
現場でのシステム操作以外の作業も観察しつつ、負担にならない最適な操作フローを
設計することを意識しました。
(注文ページ の各確認ダイアログの有無、受け渡しページ での状態遷移の表現方法など) -
実際の業務システムの知見を取り入れる:
飲食バイト経験者に実際のPOSシステムの設計を教えてもらい、UI改善に活かしました。
その知見から改修したのが特に、「左右2分割のカード移動式 受け渡しページ」 です。- 元々:
- 左半分: 注文リスト(呼び出し状態に関わらず全件)
- 右半分: 詳細ステータスと呼び出しボタン(選択した1件のみ)
- 改修後:
- 左半分: 呼び出し待ち注文カード、カードを押すと右へ移動
- 右半分: 呼び出し済み注文カード
改修後には実際に、使いやすくなったと好評をもらえました。
- 元々:
-
本来の業務フローに従わない操作を安全に解放:
注文状態の強制変更、修正、キャンセルなど、通常の業務フローでは行わない操作も
専用ページから行えるようにしました。
これにより、現場でのイレギュラー対応をある程度許容し、
システムが現実の業務の足枷にならないことを重視しました。
開発者(私)以外にもシステムを理解したクラスメイトに準管理者権限を付与することで、
自分以外も危険操作を行えるようにしました。 -
実際の現場を観察し改善:
予行や本番で実際の現場の状況を観察し、改善に活かしました。
特に1日目で食材の在庫管理が混乱気味だったため、
2日目に向けて在庫管理機能を追加したりもしました。
3. 文化祭特有の頻繁な仕様変更に対応できる柔軟性の確保
-
仕様を細かく定義することで差分を明確化:
仕様変更でどの部分が変わったのかを明確にするため、仕様を細かく定義しました。
これにより、変更箇所の特定と修正が容易になり、短期間での対応が可能になりました。 -
変更箇所を最小限に抑える設計:
仕様変更が発生した場合でも、影響範囲を最小限に抑える設計を心がけました。
データ管理をUIから分離することで、UIを自由に変更できるようになりました。
(これのお陰でゲスト用ページを当日朝に思いつきで追加できました)
他にもドメインレベルの多様な変更も許容できるように意識して設計しました。
(ただしこれは後に障害の一因になりました(後述))
4. 境界を引いてAIを活用することで短期開発を可能に
-
適切にAIを活用する:
決定論的な単純作業であるコーディングはAIに一任しました。
代わりに人間(私)は、AIが誤解する余地が僅かにも残らないように、
極力細かく仕様を定義することに注力しました。
1つ1つのUIコンポーネントの挙動や、if文に近い具体的な判定条件まで記述しました。
手書きな上にシステムの規模に反して1000行を大きく超える仕様書になりましたが、
仕様書を正とし、AIに追従させることで、意図に反する部分の修正や
ブラックボックス化を防ぐことができました。
4. 本番運用
本番障害から復旧、そして再発防止
迎えた本番。
予行から1日目の午前までは順調に稼働していたものの、
昼前のピーク時にシステムが突然全面停止しました。
調理担当を抜けて急いでサーバのログを確認すると、
毎秒数十件のエラーログで真っ赤に染まっていました。
Exceeded allowed rows read in Durable Objects free tier
システム開発での明確な失敗は人生初の出来事で、ものすごく動揺して
目の前が真っ暗になり、手が震えるのを抑えられなかったのを覚えています。
直接的な原因は、DurableObjects内蔵DBの無料枠における読み取り上限超過でした。
しかし間接的には、使用量制限超過だけでなく、複数の要因が重なった結果の障害でした。
- データモデルの過剰な正規化によるN+1問題が存在
- 初期の晴天版で柔軟性を意識した設計が密かに残存し裏目に
- 注文数増加後を想定した動的テストは行っていなかった
- 有料プラン契約忘れにより従量課金制に移行できず、上限到達でサーバが完全に停止
- 慣れたつもりになってDurableObjectsの料金モデルを確認していなかった
- よりにもよってRead制限のためデータを吸い出すこともできずフェイルオーバー不可
停止中、クラスメイトたちは自発的に現場をアナログ運用へ切り替えてくれました。
- 会計列には「優先券」を配って一度列を解散
- 受け渡し口では呼び出し式を中止し、紙の注文票を使った整列・手渡し運用に変更
- 端末に残っていたキャッシュデータから注文内容を部分的に抽出
即座に代替手順を作ってくれたおかげで、復旧作業に集中することができました。
(本来ならば事前にシステム停止時の手順を作っておくべきでした……)
障害開始から約20分後 、プラン変更が反映され、エラー発生が収束。
正常稼働が再開し、デジタル運用に戻すことができました。
1日目終了後には他にも致命的な問題が潜んでいる可能性を考慮し、
完全停止を根本的に防ぐためにフェイルオーバーの仕組みを整備しました。
さすがにAWS等に待機系を作る時間はなかったため、
ローカル環境に待機系を構築して切り替えられるようにしました。
(障害時にはCloudflareTunnelsで公開する予定でした。)
今回の障害ではデータの吸い出しすらできない状態に陥ったため、
元々あったデータエクスポート/インポート機能に加え、
DurableObjectsとは別でD1にWriteレプリカを作って待機系に引き継げるようにしました。
2日目の完走と完売
2日目は幸い他の障害が発生することはなく、無事に完走しました。
ホットドッグは予定していた全数完売し、盛況のうちに文化祭を終えることができました。
5. まとめ
高校最後の文化祭で、短期間での開発、急な仕様変更、本番障害、復旧、再発防止までを
一通り経験することになりました。
人生初の本番障害で、「十分対処できたはずだったのに」という後悔はもちろん強いです。
しかし、社会に出る前にシステムを現場で運用する責任を肌で知ることができたのは、
非常に大きな経験だと思っています。
また、現実世界の現場の運用に合わせたシステムを作るということの難しさも感じました。
現実と連携し、現場の人々の負担を減らすためのシステムを作ろうとすると、
いつもの趣味開発とは比べものにならないほど理想論が通用しませんでした。
今回の開発を通して、システムはコードだけで完結するものではなく、
それを使う人や現場の運用まで含めて設計する必要があることを痛感しました。
6. おわりに
一緒に企画を作り上げてくれたクラスメイト、支えてくださった先生に感謝します。
本記事で紹介した開発や運用の経験が、同じように学校行事などでシステムを作ろうとしている方の参考になれば幸いです。
最後まで読んでいただき、ありがとうございました!