テーマ
Requirements: データモデル
概要
Miss World Japan 2026 投票サイトで扱うデータの 要件レベルの定義。エンティティの存在・属性・関係・整合性ルールを実装非依存で記述する。
具体的なテーブル定義(カラム名・型・制約・ER図・マイグレーション)は
design.md、技術選定はsteering/tech.mdを参照。
エンティティ一覧
| エンティティ | 概要 | 関連spec |
|---|---|---|
| Candidate(候補者) | Miss World Japan ファイナリストの情報 | home / candidate-detail / candidate-ranking / admin |
| CandidateImage(候補者画像) | 候補者に紐づく画像本体(BLOBデータ) | home / candidate-detail / admin |
| User(ユーザー) | サイトを利用する投票者 | social-login |
| Vote(投票) | ユーザーが候補者に投じた1票 | voting-flow |
| CreditPackage(投票券パッケージ) | 購入可能な投票券の単位 | credit-purchase |
| Purchase(購入) | ユーザーが投票券パッケージを購入した記録(決済と票の紐づけを保持) | credit-purchase |
| VotingPeriod(投票期間) | 開催回ごとの投票受付期間(開始日・終了日) | home / candidate-ranking / admin |
設計判断: 候補者の画像本体はDB内にBLOBデータとして保持する。候補者数が10名前後 × 各5枚程度と限定的であり、Neon Branching によるブランチ環境ごとの完全独立性を活かすため、外部ストレージサービスを使わず DB に集約する。
エンティティ詳細
Candidate(候補者)
ファイナリストとして掲載される候補者の情報。テキスト系の表示用情報はすべてDBで管理する。
必須属性
| 属性 | 説明 | 例 |
|---|---|---|
| 識別子 | 候補者を一意に識別する値 | – |
| 表示名 | サイト上で表示される候補者の名前 | 「アリサ」 |
| 出身地 | 候補者の出身地(都道府県等) | 「東京」 |
| 年齢 | 候補者の年齢 | 23 |
| 身長 | 候補者の身長(cm) | 168 |
| 職業 | 候補者の職業 | 「モデル」 |
| 趣味・特技 | 趣味・特技の項目(複数) | 「ヨガ」「カメラ」 |
| 資格・大会実績 | 資格・大会実績の項目(複数) | 「TOEIC900点」 |
| 座右の銘 | 候補者の座右の銘 | – |
| 夢 | 候補者の夢・目標 | – |
| PRメッセージ | 候補者からの長文メッセージ(改行を含む) | – |
| 表示順 | 候補者一覧で表示する順序を制御する値 | – |
整合性ルール
- THE SYSTEM SHALL 識別子は候補者ごとに一意である
- THE SYSTEM SHALL 表示名は必須であり、空文字を許容しない
- THE SYSTEM SHALL 趣味・特技、資格・大会実績はそれぞれ0件以上のリストとして扱う
- THE SYSTEM SHALL PRメッセージは改行(複数の段落)を保持できる
- THE SYSTEM SHALL 候補者は0件以上のCandidateImageと関連付けられる
- THE SYSTEM SHALL 投票が記録されている候補者は削除しない(運用ルールで担保)
CandidateImage(候補者画像)
候補者に紐づく画像本体。データはBLOBとしてDB内に保持する。
必須属性
| 属性 | 説明 |
|---|---|
| 識別子 | 画像を一意に識別する値 |
| 関連候補者 | Candidate への参照 |
| 画像本体 | バイナリデータ(BLOB) |
| 画像形式 | MIME型(image/jpeg image/png 等) |
| 表示順 | 候補者内での画像表示順序(0をメイン画像として扱う) |
| 作成日時 | 画像が登録された日時 |
| 更新日時 | 画像が更新された日時 |
整合性ルール
- THE SYSTEM SHALL 識別子は画像ごとに一意である
- THE SYSTEM SHALL 関連候補者と画像本体・画像形式は必須である
- THE SYSTEM SHALL 画像形式は対応形式(JPEG / PNG / WebP)のいずれかに限定する
- THE SYSTEM SHALL 表示順は同一候補者内で重複しないことが望ましい(運用ルール)
- THE SYSTEM SHALL 表示順
0の画像をメイン画像として扱い、一覧表示で使用する - WHEN 候補者が削除された THEN 関連する CandidateImage もすべて削除される(連鎖削除)
- THE SYSTEM SHALL 画像本体のサイズに上限を設ける(運用上の上限値は design.md で定義)
- THE SYSTEM SHALL 画像が更新された場合、ブラウザキャッシュを破棄するための識別子(更新日時等)が付与される
User(ユーザー)
サイトにログインして投票や購入を行う利用者。
必須属性
| 属性 | 説明 |
|---|---|
| 識別子 | ユーザーを一意に識別する値 |
| 表示名 | プロバイダーから取得した名前 |
| メールアドレス | プロバイダーから取得したメール |
| 認証プロバイダー | Facebook または Instagram |
| プロバイダー側ID | プロバイダーが発行するユーザーID |
| 作成日時 | アカウントが作成された日時 |
| 最終ログイン日時 | 最後にログインした日時 |
整合性ルール
- THE SYSTEM SHALL 同一の「認証プロバイダー × プロバイダー側ID」の組み合わせは一意である(同一ユーザーが重複登録されない)
- THE SYSTEM SHALL 認証プロバイダーは Facebook / Instagram のいずれかに限定する
- THE SYSTEM SHALL ユーザーが削除された場合、紐づく投票・購入履歴は集計目的のため匿名化された状態で保持する(物理削除しない)
Vote(投票)
ユーザーが候補者に対して1票を投じた記録。
必須属性
| 属性 | 説明 |
|---|---|
| 識別子 | 投票を一意に識別する値 |
| 投票者 | User への参照 |
| 対象候補者 | Candidate への参照 |
| 投票種別 | 無料投票 または 有料投票 |
| 投票日時 | 投票が行われた日時 |
任意属性
| 属性 | 説明 |
|---|---|
| 関連する購入 | 有料投票の場合、由来となった Purchase への参照(無料投票では未設定) |
整合性ルール
- THE SYSTEM SHALL 同一ユーザーは1日(0:00〜23:59:59)に最大1件の無料投票を持てる
- THE SYSTEM SHALL 同一ユーザーの有料投票件数に上限を設けない
- THE SYSTEM SHALL 投票の対象候補者・投票者・投票日時は必須である
- THE SYSTEM SHALL 投票記録は事後変更不可(イミュータブル)として扱う
- IF 投票種別が「有料投票」 THEN システムは関連する購入への参照を必ず保持する
- IF 投票種別が「無料投票」 THEN システムは関連する購入への参照を保持しない
- THE SYSTEM SHALL 候補者に紐づく総得票数は、関連する投票レコードから集計可能である
- THE SYSTEM SHALL 候補者ごとの「投票者貢献度ランキング」は、投票レコードから投票者ごとの票数を集計して導出する
- THE SYSTEM SHALL 関連する購入が払戻されたとき、その購入由来の有料投票はすべて無効化される
CreditPackage(投票券パッケージ)
ユーザーが購入できる投票券の単位。
必須属性
| 属性 | 説明 |
|---|---|
| 識別子 | パッケージを一意に識別する値 |
| 名称 | 表示用のパッケージ名 |
| 票数 | パッケージに含まれる投票券の枚数 |
| 価格(税込) | 円単位の販売価格 |
| 「人気」表示フラグ | UI上で人気として強調するか |
| 表示順 | パッケージ一覧内での表示順序 |
| 有効フラグ | 現在販売中かどうか |
整合性ルール
- THE SYSTEM SHALL 票数は1以上の整数である
- THE SYSTEM SHALL 価格は0以上の整数(円)である
- THE SYSTEM SHALL 同時に複数のパッケージが「人気」フラグを持つことを許容する
- THE SYSTEM SHALL 購入時はその時点のパッケージ情報(名称・票数・価格)を Purchase 側にスナップショットして保存する(後でパッケージが変更されても過去の購入記録に影響しない)
初期データ
| 名称 | 票数 | 価格 | 「人気」 |
|---|---|---|---|
| 10票パッケージ | 10 | ¥1,000 | – |
| 35票パッケージ | 35 | ¥3,000 | ✓ |
| 60票パッケージ | 60 | ¥5,000 | – |
| 150票パッケージ | 150 | ¥10,000 | – |
Purchase(購入)
ユーザーが投票券パッケージを購入した記録。決済の結果を保存し、その購入由来の Vote と紐づく。
必須属性
| 属性 | 説明 |
|---|---|
| 識別子 | 購入を一意に識別する値 |
| 購入者 | User への参照 |
| 対象候補者 | Candidate への参照(購入と同時にこの候補者へ投票するため) |
| パッケージスナップショット | 購入時のパッケージ情報(名称・票数・価格)のコピー |
| 決済プロバイダー | 「Stripe」など |
| 決済プロバイダー側の取引識別子 | 決済プロバイダーが発行する取引ID |
| 決済ステータス | 処理中 / 成功 / 失敗 / 払戻 |
| 購入日時 | 購入操作が完了した日時 |
| 完了日時 | 決済が成立した日時 |
整合性ルール
- THE SYSTEM SHALL 同一の決済プロバイダー側取引識別子を持つ購入レコードは存在しない(重複決済防止)
- THE SYSTEM SHALL 購入レコードはパッケージのスナップショットを持ち、後の価格改定・パッケージ廃止に影響されない
- WHEN 決済が成功した THEN システムは「パッケージの票数 × 1票」の有料投票(Vote)レコードを生成し、各 Vote はその Purchase への参照を持つ
- WHEN 決済が失敗した THEN システムは関連する Vote レコードを生成しない
- WHEN 払戻が発生した THEN システムは購入の決済ステータスを「払戻」に更新し、その購入に紐づく有料投票(Vote)をすべて無効化する
VotingPeriod(投票期間)
開催回ごとの投票受付期間。Home画面の投票期間バッジ表示や、投票受付時の有効性判定に用いる。固定値として実装に埋め込まず、運営が管理画面から変更可能な永続データとして保持する。
必須属性
| 属性 | 説明 | 例 |
|---|---|---|
| 識別子 | 投票期間レコードを一意に識別する値 | – |
| 開始日時 | 投票受付の開始日時(タイムゾーン付き) | 2026-04-01T00:00:00+09:00 |
| 終了日時 | 投票受付の終了日時(タイムゾーン付き) | 2026-04-30T23:59:59+09:00 |
| 有効フラグ | 現在「公開中の投票期間」として扱うかどうか | true / false |
| 作成日時 | レコードが登録された日時 | – |
| 更新日時 | レコードが更新された日時 | – |
整合性ルール
- THE SYSTEM SHALL 識別子は投票期間ごとに一意である
- THE SYSTEM SHALL 開始日時・終了日時はタイムゾーン付きで保持する
- THE SYSTEM SHALL 終了日時は開始日時より後である
- THE SYSTEM SHALL 有効フラグが
trueの投票期間は同時に1件以下である(現開催を一意に特定できる) - THE SYSTEM SHALL 過去開催の VotingPeriod レコードは履歴として保持する(物理削除しない)
- WHEN Home画面およびランキング画面が投票期間を必要とする THEN システムは有効フラグが
trueのレコードを参照する - IF 有効な投票期間が存在しない THEN システムは投票期間に依存する画面表示をエラーとして扱う(Home画面の要件はそれぞれの spec を参照)
エンティティ間の関係
User ──┬──< Vote >───── Candidate ──< CandidateImage
│ │
│ │ (有料投票の場合のみ purchase_id で紐づく)
│ │
└──< Purchase >── Candidate
│
└─ CreditPackage(スナップショットとして保持)関係の詳細
| 関係 | カーディナリティ | 削除時の挙動 |
|---|---|---|
| User : Vote | 1 : 0..* | User削除時もVoteは残す(匿名化) |
| Candidate : Vote | 1 : 0..* | Candidate削除は原則禁止(投票がある場合) |
| Candidate : CandidateImage | 1 : 0..* | Candidate削除時にCandidateImageは連鎖削除 |
| User : Purchase | 1 : 0..* | User削除時もPurchaseは保持(税法上7年) |
| Candidate : Purchase | 1 : 0..* | Candidate削除は原則禁止 |
| Purchase : Vote | 1 : 1..* | Purchase払戻時、紐づくVoteを無効化 |
| CreditPackage : Purchase | 0..* (スナップショット参照) | CreditPackage削除はPurchaseに影響しない |
集計・導出されるデータ
実テーブルとしては保持せず、必要時に集計で導出する値:
| 導出データ | 集計元 | 用途 |
|---|---|---|
| 候補者の現在得票数 | Vote を candidate_id でカウント(無効化されたVoteは除外) | home / ranking で表示 |
| 候補者の現在順位 | 全候補者の得票数を降順ソート | 全画面 |
| 候補者ごとの投票者貢献度ランキング | Vote を candidate_id × user_id でグループ化、票数降順 | candidate-detail |
| ユーザーが当日無料投票したか | Vote を user_id × vote_type='free' × 当日でカウント | voting-flow |
| ユーザーの購入履歴 | Purchase を user_id で抽出 | ユーザーマイページ |
パフォーマンスが課題になる場合、得票数のみキャッシュテーブルとして保持する選択肢もある(design.md で検討)。
ライフサイクルとイベント
各エンティティの主要なライフサイクル:
Candidate
- 運営が管理画面で候補者を登録
- 関連する CandidateImage を管理画面でアップロード
- 投票期間中は表示・投票対象
- 投票期間終了後も Vote / Purchase との関連保持のためレコードは残す
CandidateImage
- 運営が管理画面でアップロード(画像本体はDB BLOBとして保存)
- 候補者の表示時に配信エンドポイント経由で取得される
- 候補者削除時に連鎖削除される
User
- ソーシャルログインで自動作成
- 削除リクエスト時に匿名化(個人情報のみ削除し、投票・購入履歴は残す)
Vote
- ユーザー操作で作成(無料)または Purchase 成功時に一括生成(有料)
- イミュータブル(変更されない)。ただし関連 Purchase の払戻時は「無効化」状態となる
Purchase
- ユーザーが決済確定操作
- 決済プロバイダーの Webhook 受信で「処理中 → 成功 / 失敗」に遷移
- 成功時、紐づく Vote レコードが一括生成される
- 払戻発生時は「払戻」に遷移し、紐づく Vote をすべて無効化する
VotingPeriod
- 運営が管理画面で次回開催の投票期間(開始日時・終了日時)を登録
- 開催開始時に「有効フラグ」を
trueにし、現開催として公開する - 開催終了後は新たな開催を登録する際に旧レコードの有効フラグを
falseに切り替える(履歴として保持)
投票無効化の扱い
払戻時に「Vote をどう無効化するか」には複数の選択肢がある(具体的なテーブル設計は design.md で確定):
- 論理削除フラグ: Vote に
is_invalidのような状態を持たせ、集計時に除外する - 物理削除: Vote レコードを削除する
- ステータス列: Vote に
status: 'active' | 'invalidated'を持たせる
要件レベルでは「無効化された投票は得票数集計に含めない」「無効化の事実は監査可能であること」を満たせれば良い。
非機能要件
- データ保存期間: 投票・購入履歴は税法上の保存期間(7年)以上保持する
- 個人情報の匿名化: ユーザー削除リクエスト時、識別不可能な形式に変換した上で投票・購入関連データは残す
- トランザクション境界: 「購入成功 → N票分の Vote 一括生成」は同一トランザクション内で実施する
- 整合性: Purchase の決済ステータスと、紐づく Vote の有効/無効状態は常に整合している必要がある
- 画像配信: CandidateImage の画像本体は配信エンドポイント経由で取得され、ブラウザ・CDNキャッシュを活用する
セキュリティ・冪等性・監査ログ等のサイト共通要件は
steering/tech.mdの「セキュリティポリシー」を参照。
依存仕様
home/requirements.md(Candidate の表示要件)candidate-detail/requirements.md(Voter ランキング、PR表示)candidate-ranking/requirements.md(得票数集計)voting-flow/requirements.md(無料/有料投票ルール)credit-purchase/requirements.md(パッケージ・Purchase 仕様)social-login/requirements.md(User 認証)admin/requirements.md(候補者・画像の管理)