投稿と再現手順を、まとめて保存する。グリッチハンターのDB設計

投稿と再現手順を、まとめて保存する。グリッチハンターのDB設計

目次

ゲームのバグを投稿するアプリには、タイトルや説明だけでなく、「どう操作すると、その現象が起きるか」という再現手順も必要になる。

Glitch Hunter Libraryのコードを読み返すと、投稿と手順を別のテーブルに保存し、両方の書き込みを一つのトランザクションにまとめていた。今回は、その実装をたどってみる。

Prisma公式の共有画像。白地にPrismaのロゴと、斜めに伸びるカラフルなライン。

画像:Prisma公式サイトの共有用画像。

企画から制作までの経緯は、以前の記事に書いた。実装はAIと進めた。この記事では、Prismaスキーマと投稿APIをもとに、保存の流れと、今後見直したい点を整理する。

対象はリポジトリの1f5e948時点。今回はコードとマイグレーションを読み、本番DBへの適用状況や実行時の動作までは確認していない。

一つの投稿に、複数の手順を持たせる#

DBはPostgreSQL、アプリからの操作にはPrismaを使っている。

主な関係はこうなっている。

テーブル保存する内容関係
Glitch投稿のタイトル、ゲーム名、説明など投稿の本体
ReproductionStep手順番号、操作内容、動画内の時刻など一つの投稿に複数件
VoteユーザーIDと投稿IDユーザーと投稿の組み合わせ

再現手順を分けておくと、「一番目の操作」「二番目の操作」という単位で扱える。ReproductionStepglitch_idで投稿を参照し、step_numberに順番を持っている。

表示時にも、この番号で並べる必要がある。DBに番号を保存しただけで、取得結果が自動的に番号順になるわけではない。

重複させたくない組み合わせを、DBに書く#

手順のモデルには、次の指定がある。

@@unique([glitch_id, step_number])

同じ投稿に、同じ番号の手順を二つ保存しないための制約だ。投稿Aの手順1と投稿Bの手順1は保存できるが、投稿Aの手順1を重複させることはできない。

投票のモデルにも、組み合わせに対する指定がある。

@@unique([user_id, glitch_id])

これは、一つのユーザーIDと一つの投稿IDの組み合わせを重複させない。ただし、一人が複数アカウントを使うことまで防ぐ仕組みではない。また、操作している本人がそのユーザーなのかを確かめる認証・認可も別に必要になる。

「一人一票」という言葉だけでまとめず、DBが実際に制限しているのはどの値の組み合わせかを確認したい。PostgreSQLの制約の説明も、この区別を考える際の参考になる。

途中で失敗したら、投稿だけ残さない#

投稿APIは、最初にGlitchを作り、そのIDを使って再現手順を作成する。この二つの処理は、Prismaの$transactionの中に置かれている。

流れを文章にすると、次のようになる。

  1. 投稿の本体を作る。
  2. 作成した投稿IDを、それぞれの手順へ付ける。
  3. 手順をまとめて保存する。
  4. 全体が成功したら、保存を確定する。

手順を保存している途中でエラーになった場合、投稿本体だけが残ることを防ぐ構成だ。API側では、受け取った手順の配列からindex + 1で番号を付けている。

ただし、手順が渡されなければ、手順なしの投稿を作ることもできる。「必ず手順付きで投稿される」という制約にはなっていない。

必要なら、配列の型、手順の件数、文字数、空の操作説明などを入力時に検証する必要がある。トランザクションが守るのは保存処理のまとまりであり、内容の妥当性まですべて保証するわけではない。

通知は、保存のあとに送っている#

DBへの保存が終わったあと、Discordへの通知処理を呼び出している。通知に失敗した場合はログに記録し、APIは作成した投稿を返す構成になっている。

通知先の不調で、保存済みの投稿まで失敗扱いにしない、という分け方だ。

一方、この呼び出しは待ち合わせておらず、永続的なキューもこの箇所にはない。実行環境がレスポンス後に処理を止める場合や、一時的な通信失敗があった場合の配信保証はない。

通知を必ず届ける必要があるなら、保存と一緒に通知待ちの記録を残し、別の処理で再送できるようにする設計が候補になる。これは今後の改善案で、実装済みの機能ではない。

動いた先に、説明できる範囲を増やす#

今回読んだ範囲では、投稿と手順をまとめて保存し、番号の重複をDBで制限し、外部通知を保存後に分けていることが分かった。

次に検証するなら、手順の保存を意図的に失敗させたときに投稿本体が残らないこと、制約違反をどう利用者へ返すか、通知に失敗しても投稿を確認できることを、ローカルの検証用DBで確かめたい。今回はそこまでの実行検証はしていない。

AIと作ったアプリでも、保存が途中で止まるとどうなるのか、どこで重複を防ぐのか、外部サービスが止まるとどうなるのかは、コードへ戻って説明できるようにしたい。

画面からは見えないけれど、運用を続けるときに必要になる部分だと思う。


確認したソース:

Related Post
Astroで画像のホバーCSSが効かなかった原因
image

このブログを直している途中で、「ホバー時の画像のCSS変えた?」と聞いたことがある。

記事カードの画像は、普段は少し色を落とし、マウスを重ねると元の色に戻るようにしている。その表示を直した履歴を読むと、変更したのはCSSではなく、画像コンポーネントの属性の受け渡しだった。

Meta Data
公開日:2026-09-19
Tags:
#個人開発
#tech
「新しい順」だけでは足りなかった。記事の並び順を考える
image

このブログで、グリッチハンターの記事をピン留めしてほしいと頼んだあと、「先頭になってない」と伝えたことがある。

記事のデータにピン留めの印を付けることと、画面の先頭へ並べることは、別の処理だった。記事を増やしながら、何を基準に並べるかも決める必要が出てきた。

Meta Data
公開日:2026-09-19
Tags:
#個人開発
#tech
Astroブログの下書きを、一覧・RSS・サイトマップからも除外する
image

このブログは、AIと下書きを作り、ローカルで読んでから公開している。下書きは手元で確認したい。でも、本番には出したくない。

そのために記事へ付けているのが、draft: trueだ。今回整理したのは、この値を記事ページだけでなく、一覧やRSSなどの出力にも反映する処理だった。

Meta Data
公開日:2026-09-18
Tags:
#個人開発
#tech
AstroブログのSEOを整えた。メタデータとSearch Consoleで確認したこと
image

このAstroブログのSEO周りを、Codexと一緒に整えた。canonical、OGP、記事の構造化データを確認し、Google Search Consoleも設定した。

その中で気になったのが、「memida」と検索するとContactが上の方に出てくることだった。ページを直したことと、検索結果が変わったことを混同しないように、作業と確認結果を分けて残しておく。

Meta Data
公開日:2026-09-18
Tags:
#個人開発
#tech