デジタル食券システム「E-Syoku」開発記実装編
前書き
この記事はE-Syokuについての開発記です。
実装について、要件定義など、開発期間におけるいろいろなことをまんべんなく拾って書いていきます。(なお、執筆時点で開発開始から3か月ほど経過しております)
やるきがあれば追記します。
実際の画面はこっちの記事に乗せています。
企画の始まり
毎年のように夏前になると、「今年は文化祭なにしよう。」というタイミングがあります。
今年は、毎年昼時にグラウンドに発生するくそ長い長蛇の列を解消することにしました。
とても昔からなんとなく問題意識はありましたが、うちの学校は土地柄グラウンドが非常に狭く、そのグラウンドで飲食物の販売をしてしまうがために、毎年多くのお客さんが狭いグラウンドに詰めかけてしまっていました。
去年は私も直接目にする機会があり、炎天下にお客さんが長蛇の列をなしている状況を理解しました。
ということで、お客さんがお店の前などに並ぶ時間を削減するため、E-Syokuプロジェクトを企画しました。
企画概要
お客さんが商品の注文をオンラインで行い、現金決済後、食券を発行。
通知で商品の出来上がりをお知らせし、受け取りに来てもらう。
裏で、商品の在庫も自動で更新し過売を防ぐ。
なるべく、人間の介入・操作を必要としないながらも、商品の販売を効率的に行う理想のシステム・・・。
商品の受け取り矛盾編
お店「出来上がった商品を置くスペースがあんまりないから、お客さんが目の前にいないと困る!」
お客さん「目の前で待ちたくない!!!!!」
企画会議の時点で発覚した矛盾です。お店はお客さんがお店の目の前にいることを期待しますが、そもそもこの企画はお客さんがお店の目の前で待たなくてもよいように始めたものです。
<この矛盾の解決方法>
諦めました。世界平和は達成されません。
商品ができていなくても、一定数のお客さんはお店の前に来てもらうように通知を送信するようにしました。
つまり、本来ならば並ばなければいけなかった列の最後の部分だけ本当に並んでもらうということです。
Let's 実装
(Let usなのであなたも一緒に楽しいコーディングタイムです)
全体のRepo
バックエンド
バックエンドは去年からお世話になっているFirebaseです。とても愛しています。
- Functions(API実装)
- Firestore(DB)
- Authentication(認証)
- Cloud Messaging(通知送信)
- Hosting(デプロイ)
以下、基本定期にFunctionsにデプロイしたAPI側のソースコードについて書いていきます。
また、当該ソースコードはここにあります。
Zod
TSはあくまでもドキュメンテーションであり、実行時の型情報を保証するものではあまりない、(という事実さえあまりまともに受け止めていませんでしたが)、ということで確実に型の安全を確認してくれるZodを信頼しました。
Cloud Messaging
Cloud MessagingはFirebaseの機能の一つで、簡単にマルチプラットフォームに対しての通知を送信できます。このシステムもこれを利用しています。
ちなみに、Webにおけるプッシュ通知はiOSではiOS16.4から有効です。
恐ろしく、必要バージョンが高いです。おのれApple 執筆時点で日本ではおよそ半分のユーザーが16.4以降に更新したという情報がありますが、正直十分多くの人が更新を済ませた、とは言い難いでしょう。
エラーハンドリング
このシステムでは決済情報などを扱うにあたって、内部で発生した例外をクライアント側に伝えるために、エラーハンドリングを実装しました。
今考えれば、そこまでする必要はあんまりなかったと思うのですが。
クライアント側では受信したエラーデータのエラーコードをもとに、自動でドキュメントを開くようにしました。
フロントエンド
フロントエンドでは、「最近有名らしい」という理由だけで未経験のNext.jsを採用しました。
Reactも完全に知識0だったため、最初の一か月ぐらいはReactになれるのに使いました。
型情報を保ちながらasyncなロジックをくみ上げやすかったように思いました。
ソースコードはここです。
Zod
前述したZodをフロントエンドでも使用し、事前に定義したスキームをもとに、データの型チェックを実施しました。
また、(人間には全く読めるものではありませんが)型情報がエディタにも提供されることも開発の中で助けとなりました。フロントエンド開発をしている、が、それでも型チェックが利く・・・きもちいいですね。
(2回目)
APIEndpoint
システム上、バックエンドとの通信機会がかなり多めです。
開発の初期にこれからの心労を軽減するためuseLazyEndpoint
なるHookが作られました。
認証情報をProviderから取得し、tokenをheaderに加えたのち、リクエストデータをPOST
します。そして、レスポンスデータを前述のZodの力で型安全性を担保しました。
相当時間がかかったので軽くここに説明を書いておきます。
Endpoints.ts
に書いておきました。
これらの型情報をもとにして、fetchを行い、
という処理をHookにします。
このソースをはじめの方に書いておいてよかった。実際にはこのHookをつかったコンポーネントを多用しました。(Render終わった後にCreateするのはよくないらしいが私は知りません。そんな心持ではいつまで経っても完成させられない。)
useSavedState
商品の注文中に、「画面閉じちゃった」などが発生するとStateが吹っ飛び、かなりかわいそうな事態が発生します。
それを防ぐために、StateとURLのパラメターを同期する関数を強引に作りました。
これのおかげで、データが飛ぶことはなくなりましたが、保存する内容が増えるごとにURLがくそ長くなっていきます。きれいですね。
Chakra UI
開発しているうちに、「あれ欲しいなぁ」っていうのが大体揃っていて良かったです。
基本的にはStack系とCard、Modalを多用しました。
関数を呼び出すだけで画面にModalが出てくるようなものも考えましたが、そんなにきれいに書けそうになかったのであきらめました。
PWA化
最近のスマートフォンとかだと、通知送信の権限を要求しても、完全に無視されることがあることが発覚したので、PWA化を行いました。
アプリの利用にはPWAインストール必須としました。
あとがき
なんか何使ったという話ばかりであまりソースコードに触れられませんでした。ごめんなさい。
(書いてるうちにどうでもよくなってきちゃった)
この実装編では内部的な実装についての話を軽くまとめましたが、実際に運用予定なのでそのうち運用編も書いてあげると思います。
Discussion