靴をなくしたので、アプリを作ることにした vol2 配信駆動開発
前回の記事
前回の記事はこちら。
閲覧数や、Like の割に、フォローしてくれる人が多かったので、記事を書くモチベーションになりました。ありがとうございます。
靴
は、まだ見つかっていない。ジムでなくした可能性が高いので、電話番号を伝えてきた。あと、安い靴を注文した。1500円。なくしても、そんなに悲しくない。
話は変わるけど、X で Youtube で配信しながら開発することを、配信駆動開発と名付けていた方がいた。であれば、記事を書きながら開発するのは、記事執筆駆動開発とでも読んだらいいのだろうか。
今回の目標: DBを決める & DB設計
DBを決める
前回、DB は Deno と相性のいいものを使うと書いた。Deno Deploy を触っていたら、Deno KV なるものの存在に気づいた。KV とは Key-Value の略だろう。cron と同じで、beta 版なので、安定しているかわからない。これを使ってみたいのだが、そもそも Deno から、DB に書き込んだりする必要がないじゃないか。このアプリにわざわざ、API 経由で書き込むのは、必要ない。クライアントから書き込めば良い。
Firestore を使おう。Android の Push 通知にもどうせ Firebase の設定が必要である。従量課金なので、まず僕しか使わない段階では無料で済む。
DB設計
まず、通知の間隔をユーザーに決めてもらうための設計を考える。
- 最小の通知間隔で定期実行 (おそらく 1 時間)
- ユーザーに紐づく情報を Firestore から取得
- 通知間隔を確認
- 最後の通知時間を確認
- 現在の時間 - 最後の通知時間 > 通知間隔 なら通知
- 通知完了後、通知時間を書き込む
効率が悪そうであるが、これ以外に方法が思いつかない。
毎時全てのデータを舐めることになるが、ユーザーが僕だけなら何の問題もない。しかも Firestore は、レスポンスが結構早い(と思っている)。仮にレスポンスが遅くても、通知が数秒遅れるだけで、それはそれで問題ない。
もしもユーザーが増えてしまったら、料金的に問題になるかもしれない。嬉しい悲鳴だが、それはその時考えることにしよう。
次に、Firestore のデータ構造を考える。TypeScript の型でデータ構造を表す。
type User = {
tokens: string[];
}
type Trip = {
destination: string;
startDate: Timestamp;
endDate: Timestamp;
}
type Item = {
name: string;
notificationInterval: number; // 通知間隔(分)
lastNotifiedAt: Timestamp; // 最後の通知日時
isNotifyEnabled: boolean; // 通知のオンオフ
}
各コレクションは、親子関係なので、互いの ID を持つ必要は特にない。
users/{userId}/trips/{tripId}/items/{itemId}
使う側のイメージとしては
- 旅行を登録
a. 目的地と、旅行期間を登録 - 通知したい項目を登録
a. 通知間隔を設定 (1 時間、3 時間、6 時間)
b. 通知をオンオフできる - 通知を受け取る
a. 通知を受け取って、アプリに遷移したら、モーダルが出て、[持っている / 無くした / 確認していない] を選択する (設計に考慮していなかった) - 物を無くす
a. 何時間前まで持っていたかを確認する
b. 泣きながら探す
自分で書いていて思った。物をなくす前提でアプリを作るのは、なんて悲しいんだ。
確認した時間を追加する。
type Item = {
name: string;
notificationInterval: number; // 通知間隔(分)
lastNotifiedAt: Timestamp; // 最後の通知日時
isNotifyEnabled: boolean; // 通知のオンオフ
lastConfirmedAt: Timestamp; // 最後に確認した日時
}
設計はほぼ終わった。次回は、クライアント側のデザインを考える。
またね:)
おまけ
Chat GPT に聞いたらこうなった。
type Token = {
value: string;
deviceType: string; // "ios" | "android"
createdAt: Timestamp;
}
type User = {
tokens: Token[];
}
type Trip = {
destination: string;
startDate: Timestamp;
endDate: Timestamp;
description?: string; // 旅行の説明など
}
type NotificationSchedule = {
intervalMinutes?: number;
intervalDays?: number;
specificTimes?: Timestamp[];
}
type Item = {
name: string;
notificationSchedule: NotificationSchedule;
lastNotifiedAt: Timestamp;
isNotifyEnabled: boolean;
lastConfirmedAt: Timestamp;
history?: ItemNotificationHistory[]; // 任意で履歴を保存
}
type ItemNotificationHistory = {
notifiedAt: Timestamp;
confirmedAt?: Timestamp;
}
まあわかるけど、個人開発でこれは過剰なので、無視。
Discussion