Skip to content

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 : Vote1 : 0..*User削除時もVoteは残す(匿名化)
Candidate : Vote1 : 0..*Candidate削除は原則禁止(投票がある場合)
Candidate : CandidateImage1 : 0..*Candidate削除時にCandidateImageは連鎖削除
User : Purchase1 : 0..*User削除時もPurchaseは保持(税法上7年)
Candidate : Purchase1 : 0..*Candidate削除は原則禁止
Purchase : Vote1 : 1..*Purchase払戻時、紐づくVoteを無効化
CreditPackage : Purchase0..* (スナップショット参照)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

  1. 運営が管理画面で候補者を登録
  2. 関連する CandidateImage を管理画面でアップロード
  3. 投票期間中は表示・投票対象
  4. 投票期間終了後も Vote / Purchase との関連保持のためレコードは残す

CandidateImage

  1. 運営が管理画面でアップロード(画像本体はDB BLOBとして保存)
  2. 候補者の表示時に配信エンドポイント経由で取得される
  3. 候補者削除時に連鎖削除される

User

  1. ソーシャルログインで自動作成
  2. 削除リクエスト時に匿名化(個人情報のみ削除し、投票・購入履歴は残す)

Vote

  1. ユーザー操作で作成(無料)または Purchase 成功時に一括生成(有料)
  2. イミュータブル(変更されない)。ただし関連 Purchase の払戻時は「無効化」状態となる

Purchase

  1. ユーザーが決済確定操作
  2. 決済プロバイダーの Webhook 受信で「処理中 → 成功 / 失敗」に遷移
  3. 成功時、紐づく Vote レコードが一括生成される
  4. 払戻発生時は「払戻」に遷移し、紐づく Vote をすべて無効化する

VotingPeriod

  1. 運営が管理画面で次回開催の投票期間(開始日時・終了日時)を登録
  2. 開催開始時に「有効フラグ」を true にし、現開催として公開する
  3. 開催終了後は新たな開催を登録する際に旧レコードの有効フラグを false に切り替える(履歴として保持)

投票無効化の扱い

払戻時に「Vote をどう無効化するか」には複数の選択肢がある(具体的なテーブル設計は design.md で確定):

  1. 論理削除フラグ: Vote に is_invalid のような状態を持たせ、集計時に除外する
  2. 物理削除: Vote レコードを削除する
  3. ステータス列: 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(候補者・画像の管理)