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

データベーススキーマはアプリが情報をどう記憶するかの設計図です。実務的に言えば、それは:
スキーマが実際の作業に合っているとき、それは人々が実際に行うこと—作る、レビューする、承認する、スケジュールする、割り当てる、キャンセルする—を反映します。ホワイトボード上で綺麗に見えるものとは限りません。
ユーザーストーリーと受け入れ基準は、誰が何をして、何が「完了」なのかを平易に示します。これらを起点にすると、重要な詳細(「返金を誰が承認したかを追跡する必要がある」や「予約は複数回リスケ可能」など)を見落としにくくなります。
ストーリーに基づいて設計することでスコープにも正直になれます。ストーリー(やワークフロー)に書かれていないなら、それは任意の機能として扱い、「念のため」に複雑なモデルを勝手に作らないようにします。
AIは次の点で作業を早めてくれます:
AIが苦手なこと:
AIは強力なアシスタントとして扱い、最終判断は人間が行ってください。
進め方を加速したい場合、Koder.ai のような vibe-coding プラットフォームは、スキーマの決定から React + Go + PostgreSQL の動くアプリまでをより速く移行できる支援になります—ただし、モデル、制約、マイグレーションの管理はあなたが保持します。
スキーマ設計はループです:下書き → ストーリーで検証 → 欠落データを見つける → 改善。目的は最初から完璧な出力を得ることではなく、各ユーザーストーリーに遡って「このワークフローに必要なものをすべて保存できる」と説明できるモデルを作ることです。
要件をテーブルに落とす前に、あなたが何をモデリングしているのかを明確にします。優れたスキーマは白紙から始まることは稀で、実際の作業(画面、出力、エッジケース)から始まります。
ユーザーストーリーは見出しですが、それだけでは不十分です。次を集めてください:
AIを使う場合、これらのインプットがモデルを現実に即したものに保ちます。AIは素早くエンティティやフィールドを提案できますが、実際のアーティファクトがないと製品に合わない構造を作りがちです。
受け入れ基準には、たとえ明示的にデータを述べていなくても重要なDBルールが含まれていることが多いです。たとえば:
曖昧なストーリー(「ユーザーはプロジェクトを管理できる」)は複数のエンティティやワークフローを隠します。キャンセル、リトライ、部分返金、再割当などのエッジケースが漏れやすいのも一般的なギャップです。
テーブルや図を考える前に、ユーザーストーリーを読んで名詞をハイライトしてください。要件文の名詞は一般的にシステムが記憶すべき「もの」を指し、これがエンティティになることが多いです。
簡単な心構え:名詞はエンティティへ、動詞はアクションやワークフローへ。たとえば「マネージャーが技術者をジョブに割り当てる」とあれば、エンティティ候補は manager, technician, job で、「割り当てる」は後でモデル化するリレーションを示します。
すべての名詞がテーブルに値するわけではありません。名詞がエンティティ候補として強いのは:
一度しか出てこない名詞や単に説明的なもの("red button"、"Friday")はエンティティでない可能性が高いです。
すべての詳細をテーブル化する誤りを避けるための指針:
例:
Address は独立したエンティティ。単一の郵送先だけで再利用しないなら属性として十分。AIはストーリーをスキャンして、テーマ別に候補名詞の下書きを返すことで速度を上げてくれます。役立つプロンプトは「保存すべき名詞を抽出し、重複/同義語をグループ化して」といったものです。
出力は出発点として扱い、次のようなフォローアップを行ってください:
ステップ1のゴールは、各エンティティを具体的なストーリーに紐づけて防御できる短く明快なリストを作ることです。
エンティティ(例: Order, Customer, Ticket)を名前付けしたら、次は後で必要になる詳細を捕捉します。データベースではそれがフィールド(属性)です。
まずユーザーストーリーを読み、受け入れ基準をチェックリストのように扱います。
要件に「ユーザーが納品日で注文をフィルタできる」とあれば delivery_date は必須フィールドです。「誰が承認したかといつかを表示する」とあれば approved_by と approved_at が必要になります。
実用的なテスト:表示、検索、ソート、監査、計算にその値が必要か? 必要ならフィールドにします。
First name と Last name は別々に保存する。複数値を1つのフィールドに詰め込まない(例: "red, blue")。多くのストーリーに "status" や "type"、"priority" が含まれます。これらは制御語彙(許容値の有限集合)として扱ってください。
status_codes)を使う。こうしてストーリーは検索・レポート可能で誤入力しにくいフィールドに変わります。
エンティティ(User, Order, Invoice, Comment 等)とフィールドを出したら、それらをつなげます。リレーションシップはストーリーが示す「もの同士のやり取り」を表します。
1対1(1:1):一つのものがもう一つのものをちょうど一つ持つ。
User ↔ Profile(特別な理由がない限り統合できる)1対多(1:N):一つのものが多くの別のものを持つ。最も一般的。
User → Order(Order に user_id を置く)多対多(M:N):多くのものが多くのものに関連する。追加のテーブルが必要。
データベースに単に Order 内に製品IDのリストを入れるのは後で問題になります。代わりに関係そのものを表す結合テーブルを作ります。
例:
OrderProductOrderItem(結合テーブル)OrderItem は通常次を含みます:
order_idproduct_idquantity, unit_price, discount など)関係上の詳細(例: 数量)はエンティティ側ではなく関係上に置くのが一般的です。
ストーリーはリンクが必須か省略可能かを教えてくれます。
Order は user_id を必須にする(NULL不可)phone は NULL 可shipping_address_id はデジタル商品では空でも良い簡単なチェック: ストーリーがレコード作成時にリンクが必須と言っているかを基準にしてください。
ストーリーを読みながらこう書き換えていきます:
User 1:N CommentComment N:1 Userすべての相互作用についてこれを行えば、作業が実際にどう行われるかに沿った接続モデルが出来上がります—ER図ツールを開く前に。
ストーリーを読み、システムが覚えておくべき“もの”(名詞)をハイライトします(例: Ticket, User, Category)。
エンティティに昇格させる条件:
短く、各候補を特定のストーリー文に紐づけて説明できるリストにまとめてください。
「属性 vs. エンティティ」のテストを使います:
customer.phone_number)。ヒント: "many(複数)" が必要になりそうなら別テーブルを検討してください。
受け入れ基準を「保存すべき項目のチェックリスト」として扱います。要件でフィルタ/ソート/表示/監査が必要なら、それを保存する(または確実に派生できるようにする)必要があります。
例:
approved_by, approved_atdelivery_dateemail に一意制約/インデックスを追加ストーリー文を関係の文に書き換えます:
orders に customer_id を置く)order_items のような結合テーブルを追加)関係自体にデータ(数量、価格、役割など)がある場合、そのデータは結合テーブル上に置きます。
M:N は結合テーブルでモデル化し、両方の外部キーと関係固有のフィールドを持たせます。
典型パターン:
ordersproductsorder_items に , , , ワークフローをステップごとに辿り、「後でこれが起きたことを証明するには何が必要か?」と問います。
よく追加されるもの:
submitted_at, closed_atcreated_by, , まずは次を追加してください:
id)orders.customer_id → customers.id)その後、最もよく使う検索に合わせたインデックスを追加します(例: , , )。推測のインデックスは実際のクエリが遅くなるまで後回しに。
簡単な整合性チェックを実行します:
Phone1/Phone2 のような繰り返しグループがあれば子テーブルに分割する。パフォーマンスやレポートのために後から非正規化するのはあり得ますが、その意図と更新ルールを文書化してください。
再現できない事実は保存し、それ以外は計算します。
保存すべきもの:
計算すべきもの:
のような派生値を保存する場合は、同期方法とエッジケース(払い戻し、編集、部分出荷)を明確にしてテストしてください。
AIを草案生成に使い、その後実際の資料で検証します。
実用的なプロンプト例:
ガードレール:
order_idproduct_idquantityunit_price単一カラムに「IDのリスト」を入れるのは避けてください。クエリ、更新、整合性の管理が困難になります。
assigned_toclosed_byrejection_reason「誰がいつ状態を変えたか」を追いたいなら、単一フィールドを上書きするのではなくイベント/監査テーブルを追加してください。
emailcustomer_idstatus + created_atorder_total