ユーザーストーリーからデータベーススキーマへ:AIが導く実践的手法
ユーザーストーリー、エンティティ、ワークフローを具体的なデータベーススキーマに落とし込む実践的方法と、AIがギャップやルールのチェックでどう役立つかを学ぶ。

実際の作業に合うスキーマを作るということ
データベーススキーマはアプリが情報をどう記憶するかの設計図です。実務的に言えば、それは:
- テーブル:情報の「バケツ」(Customers、Orders、Tickets)
- フィールド(カラム):各対象について保存する詳細(customer_name、order_date)
- リレーションシップ:バケツ同士のつながり(Order は 1 人の Customer に属し、Customer は複数の Order を持つ)
スキーマが実際の作業に合っているとき、それは人々が実際に行うこと—作る、レビューする、承認する、スケジュールする、割り当てる、キャンセルする—を反映します。ホワイトボード上で綺麗に見えるものとは限りません。
なぜユーザーストーリーから始めるのか
ユーザーストーリーと受け入れ基準は、誰が何をして、何が「完了」なのかを平易に示します。これらを起点にすると、重要な詳細(「返金を誰が承認したかを追跡する必要がある」や「予約は複数回リスケ可能」など)を見落としにくくなります。
ストーリーに基づいて設計することでスコープにも正直になれます。ストーリー(やワークフロー)に書かれていないなら、それは任意の機能として扱い、「念のため」に複雑なモデルを勝手に作らないようにします。
AIがここでできること・できないこと
AIは次の点で作業を早めてくれます:
- 候補となるエンティティ(ストーリー内の重要な「もの」)を抽出する
- 受け入れ基準から暗黙のフィールド(タイムスタンプ、ステータス、参照)を提案する
- ありそうなリレーションシップや欠落(「承認者を保存していない」など)を指摘する
AIが苦手なこと:
- あなたが書き残していない隠れた業務ルールやエッジケースを確実に把握する
- トレードオフ(シンプル vs 柔軟)なしに「正しい」詳細レベルを選ぶ
- レポーティング、セキュリティ、コンプライアンスの要件に確実に適合させる
AIは強力なアシスタントとして扱い、最終判断は人間が行ってください。
進め方を加速したい場合、Koder.ai のような vibe-coding プラットフォームは、スキーマの決定から React + Go + PostgreSQL の動くアプリまでをより速く移行できる支援になります—ただし、モデル、制約、マイグレーションの管理はあなたが保持します。
期待値を設定する:一発で完成するものではない
スキーマ設計はループです:下書き → ストーリーで検証 → 欠落データを見つける → 改善。目的は最初から完璧な出力を得ることではなく、各ユーザーストーリーに遡って「このワークフローに必要なものをすべて保存できる」と説明できるモデルを作ることです。
インプット:ユーザーストーリー、受け入れ基準、実例
要件をテーブルに落とす前に、あなたが何をモデリングしているのかを明確にします。優れたスキーマは白紙から始まることは稀で、実際の作業(画面、出力、エッジケース)から始まります。
一箇所に揃えておきたい典型的なインプット
ユーザーストーリーは見出しですが、それだけでは不十分です。次を集めてください:
- ユーザーストーリー + ロール(誰が何のために何をするか)
- 受け入れ基準(「必ず成り立つ」ルール)
- フォーム/画面(ユーザーが入力・選択・確認するフィールド)
- レポート/エクスポート(要約・グループ・フィルタの要件)
- 実例データ(サンプル注文、請求書、チケット、カレンダー等)
AIを使う場合、これらのインプットがモデルを現実に即したものに保ちます。AIは素早くエンティティやフィールドを提案できますが、実際のアーティファクトがないと製品に合わない構造を作りがちです。
受け入れ基準:制約の隠れた源泉
受け入れ基準には、たとえ明示的にデータを述べていなくても重要なDBルールが含まれていることが多いです。たとえば:
- 「メールは一意である」→ 一意性
- 「ステータスは Draft, Submitted, Approved」→ 許容値
- 「承認できるのはマネージャーだけ」→ 権限、監査フィールドの必要性
- 「支払いがある請求書は削除できない」→ 参照制約
早めに直すべき一般的な落とし穴
曖昧なストーリー(「ユーザーはプロジェクトを管理できる」)は複数のエンティティやワークフローを隠します。キャンセル、リトライ、部分返金、再割当などのエッジケースが漏れやすいのも一般的なギャップです。
モデル化前のストーリー品質チェックリスト
- アクター/ロールが明示されているか
- オブジェクトが具体的か(「データ」「もの」ではない)
- 少なくとも1つの実例があるか
- 受け入れ基準にバリデーションや境界条件が含まれているか
- エラーや「もし〜なら」のケースが言及されているか(または明示的に保留されているか)
ステップ1 — ストーリーからエンティティを抽出する(名詞を拾う)
テーブルや図を考える前に、ユーザーストーリーを読んで名詞をハイライトしてください。要件文の名詞は一般的にシステムが記憶すべき「もの」を指し、これがエンティティになることが多いです。
簡単な心構え:名詞はエンティティへ、動詞はアクションやワークフローへ。たとえば「マネージャーが技術者をジョブに割り当てる」とあれば、エンティティ候補は manager, technician, job で、「割り当てる」は後でモデル化するリレーションを示します。
その名詞が真のエンティティかを見極める方法
すべての名詞がテーブルに値するわけではありません。名詞がエンティティ候補として強いのは:
- 固有の識別子を持つ:特定のインスタンスを指せる(例: Job #1042)
- 時間で変化する:ライフサイクルを持つ(scheduled → completed)
- 複数箇所で使われる:複数のストーリーやワークフローが参照する
一度しか出てこない名詞や単に説明的なもの("red button"、"Friday")はエンティティでない可能性が高いです。
属性か別エンティティか("Address" と "Tag" のテスト)
すべての詳細をテーブル化する誤りを避けるための指針:
- 単一の値で一つのものを表すなら属性にする(例: Customer.phone_number)
- 繰り返し可能、共有、構造化されるなら別エンティティにする
例:
- Address:配送・請求先アドレスを保持し履歴や再利用が必要なら
Addressは独立したエンティティ。単一の郵送先だけで再利用しないなら属性として十分。 - Tag:タグは繰り返し・多対多で使われることが多いので、ほとんどの場合別エンティティにする。
AIを使った候補エンティティの提案(慎重に)
AIはストーリーをスキャンして、テーマ別に候補名詞の下書きを返すことで速度を上げてくれます。役立つプロンプトは「保存すべき名詞を抽出し、重複/同義語をグループ化して」といったものです。
出力は出発点として扱い、次のようなフォローアップを行ってください:
- 「どれがライフサイクルを持ちIDを必要とするか?」
- 「どれがステータスやカテゴリ、属性に過ぎないか?」
- 「同義語はあるか(例: ‘client’ と ‘customer’)」
ステップ1のゴールは、各エンティティを具体的なストーリーに紐づけて防御できる短く明快なリストを作ることです。
ステップ2 — 詳細をフィールドにする(保存すべきリマインダー)
エンティティ(例: Order, Customer, Ticket)を名前付けしたら、次は後で必要になる詳細を捕捉します。データベースではそれがフィールド(属性)です。
フィールドを推測せずに選ぶ方法
まずユーザーストーリーを読み、受け入れ基準をチェックリストのように扱います。
要件に「ユーザーが納品日で注文をフィルタできる」とあれば delivery_date は必須フィールドです。「誰が承認したかといつかを表示する」とあれば approved_by と approved_at が必要になります。
実用的なテスト:表示、検索、ソート、監査、計算にその値が必要か? 必要ならフィールドにします。
クリーンなフィールドにするための簡単なルール
- 値は原子に保つ:
First nameとLast nameは別々に保存する。複数値を1つのフィールドに詰め込まない(例: "red, blue")。 - 型を一貫させる:日付は日付型、金額は小数、ブールは true/false。"$10" や "10 USD" のような混在は避ける。
- テキストを重複させない:顧客住所を注文ごとにコピーしない。正しい場所に一回保存して参照する。
制御語彙:ステータス、タイプ、カテゴリ
多くのストーリーに "status" や "type"、"priority" が含まれます。これらは制御語彙(許容値の有限集合)として扱ってください。
- 小さくて安定している集合なら enum スタイルのフィールドで良い。
- 成長する可能性があり、ラベルや権限が必要なら lookup テーブル(例:
status_codes)を使う。
こうしてストーリーは検索・レポート可能で誤入力しにくいフィールドに変わります。
ステップ3 — エンティティをリレーションで結ぶ
エンティティ(User, Order, Invoice, Comment 等)とフィールドを出したら、それらをつなげます。リレーションシップはストーリーが示す「もの同士のやり取り」を表します。
3つのリレーション形(平易な説明)
1対1(1:1):一つのものがもう一つのものをちょうど一つ持つ。
- ストーリーフレーズ: “各ユーザーは一つのプロフィールを持つ”
- モデル案:
User↔Profile(特別な理由がない限り統合できる)
1対多(1:N):一つのものが多くの別のものを持つ。最も一般的。
- ストーリーフレーズ: “ユーザーは多くの注文を持つことができる”
- モデル案:
User→Order(Orderにuser_idを置く)
多対多(M:N):多くのものが多くのものに関連する。追加のテーブルが必要。
- ストーリーフレーズ: “注文はいくつもの製品を含み、製品はいくつもの注文に含まれることがある”
M:N:結合テーブルの手法
データベースに単に Order 内に製品IDのリストを入れるのは後で問題になります。代わりに関係そのものを表す結合テーブルを作ります。
例:
OrderProductOrderItem(結合テーブル)
OrderItem は通常次を含みます:
order_idproduct_id- ストーリーからの追加詳細(
quantity,unit_price,discountなど)
関係上の詳細(例: 数量)はエンティティ側ではなく関係上に置くのが一般的です。
必須か任意か(専門用語抜きで)
ストーリーはリンクが必須か省略可能かを教えてくれます。
- “注文はユーザーに属する必要がある” →
Orderはuser_idを必須にする(NULL不可) - “ユーザーは電話番号を持つことがある” →
phoneは NULL 可 - “物理商品なら配送先がある” →
shipping_address_idはデジタル商品では空でも良い
簡単なチェック: ストーリーがレコード作成時にリンクが必須と言っているかを基準にしてください。
ストーリー文をリレーション文に書き換える
ストーリーを読みながらこう書き換えていきます:
- “ユーザーは多くのコメントを残せる” →
User1:NComment - “コメントは一人のユーザーに属する” →
CommentN:1User
すべての相互作用についてこれを行えば、作業が実際にどう行われるかに沿った接続モデルが出来上がります—ER図ツールを開く前に。
よくある質問
ユーザーストーリーからどのようにデータベースのエンティティを抽出しますか?
ストーリーを読み、システムが覚えておくべき“もの”(名詞)をハイライトします(例: Ticket, User, Category)。
エンティティに昇格させる条件:
- 独自のIDが必要である
- 時間の経過で変化する(ライフサイクル/ステータスがある)
- 複数のストーリーで参照される
短く、各候補を特定のストーリー文に紐づけて説明できるリストにまとめてください。
いつそれをフィールドにし、いつ別テーブルにしますか?
「属性 vs. エンティティ」のテストを使います:
- レコード一件を説明する単一の値ならフィールドにします(例:
customer.phone_number)。 - 繰り返し可能、共有される、構造化される、履歴が必要なら別テーブルにします(例: 複数の住所、タグ、添付ファイル)。
ヒント: "many(複数)" が必要になりそうなら別テーブルを検討してください。
受け入れ基準はどのようにフィールドや制約に変換されますか?
受け入れ基準を「保存すべき項目のチェックリスト」として扱います。要件でフィルタ/ソート/表示/監査が必要なら、それを保存する(または確実に派生できるようにする)必要があります。
例:
- 「誰が承認したかといつかを表示する」→
approved_by,approved_at - 「納期でフィルタする」→
delivery_date - 「メールは一意であるべき」→
emailに一意制約/インデックスを追加
ストーリー文をテーブルの関係(1:1、1:N、M:N)にどう変換しますか?
ストーリー文を関係の文に書き換えます:
- “顧客は多くの注文を持てる” → 1:N(
ordersにcustomer_idを置く) - “注文は多くの製品を含む” → M:N(
order_itemsのような結合テーブルを追加)
関係自体にデータ(数量、価格、役割など)がある場合、そのデータは結合テーブル上に置きます。
多対多の関係はどうモデリングすべきですか?
M:N は結合テーブルでモデル化し、両方の外部キーと関係固有のフィールドを持たせます。
典型パターン:
ordersproductsorder_itemsにorder_id,product_id,quantity,unit_price
単一カラムに「IDのリスト」を入れるのは避けてください。クエリ、更新、整合性の管理が困難になります。
ワークフローはどのようにして欠けているテーブルやフィールドを見つけるのに役立ちますか?
ワークフローをステップごとに辿り、「後でこれが起きたことを証明するには何が必要か?」と問います。
よく追加されるもの:
- タイムスタンプ:
submitted_at,closed_at - アクター:
created_by,assigned_to,closed_by - 理由/メモ:
rejection_reason
「誰がいつ状態を変えたか」を追いたいなら、単一フィールドを上書きするのではなくイベント/監査テーブルを追加してください。
まずどの制約を追加すべきですか(キー、一意性、インデックス)?
まずは次を追加してください:
- 各テーブルに安定した主キー(
id) - 関係用の外部キー(
orders.customer_id → customers.id) - 要件から得た一意性のルール(メール、請求書番号など)
その後、最もよく使う検索に合わせたインデックスを追加します(例: email, customer_id, status + created_at)。推測のインデックスは実際のクエリが遅くなるまで後回しに。
過度に正規化しすぎずに、スキーマが十分に正規化されているかどうかはどう判断しますか?
簡単な整合性チェックを実行します:
Phone1/Phone2のような繰り返しグループがあれば子テーブルに分割する。- 同じ事実が複数テーブルにあるなら、真のソースを1つにして参照にする。
- テーブルの主対象でないことを表す列があれば別に移す。
パフォーマンスやレポートのために後から非正規化するのはあり得ますが、その意図と更新ルールを文書化してください。
データベースに何を保存し、何を計算すべきかの判断基準は?
再現できない事実は保存し、それ以外は計算します。
保存すべきもの:
- イベントとタイムスタンプ
- ラインアイテムや履歴的な価格
- 監査のための「誰が何をしたか」
計算すべきもの:
- ラインアイテムから導ける合計
- 日付から判定する「期限切れか」などのフラグ
order_total のような派生値を保存する場合は、同期方法とエッジケース(払い戻し、編集、部分出荷)を明確にしてテストしてください。
スキーマ設計を速めるためにAIを安全に使うにはどうすればよいですか?
AIを草案生成に使い、その後実際の資料で検証します。
実用的なプロンプト例:
- 「これらのストーリーから候補となるエンティティと同義語を抽出して」
- 「受け入れ基準から示唆されるフィールド(タイムスタンプ、アクター、ステータス)を一覧にして」
- 「このワークフローに対して各ステップで必要なデータは何か?」
ガードレール:
- 入力は匿名化(PIIや機密データを含めない)
- スキーマと一緒に前提条件/決定ログを残す
- AI生成のマイグレーションも通常のコードレビューとして扱い、Gitでバージョン管理する