はじめに
こんにちは、岩橋聡吾です。
今回は私が実務で行っているデータモデリング手法(実際手法とは名ばかりですが…)を紹介いたします。
データモデリングは、ご存知の通り、その後のシステム開発や拡張スピード、またシステム自身のパフォーマンスにも深く関わってきます。ここをどのように設計するかが開発プロジェクトを進める上で大きなカギになります。
なぜレシートなのか
モデリングする上で、その対象となるサービス・システムのビジネスロジックや仕様を深く理解する必要があります。レシートの背景にある「物を買う」という行為は至極身近なものであり、誰しもがそのレシートが発行されるまでの流れを経験で知っています。またレシートは、その小さな紙の中に「店舗」や「商品」など、WEBサービスで馴染みがある概念も多く含まれており、訓練をする上で非常に手頃なものです(「コンビニ」なるフレーズは掴みに使わせていただきましたw)。
今回の話のエリア
今回は概念設計〜論理設計の前半までの内容を対象としています。具体的には「ある特定日のレシート一覧を出力できるシステム」のデータモデリングを行っていきます。
概念(実体)の洗い出しとパズル化
概念の洗い出し
上のレシートについて概念を洗い出してみましょう。深く考える必要はありません、先ずは上からなぞるようにリスト化していきましょう。
-概念リスト
【店名】
【店】
【電話番号】
【住所】
【日時】
【商品】
【値段】
【個数】
【総額】
【ポイント】
【店員】
とりあえずここまでで、次は概念のパズル化を考えていきましょう。
概念(実体)のパズル化
この概念のパズル化は概念同士の関係を以下のルールに当てはめながら作っていきます。
- AのB(所有) ex) 私(A)のパソコン(B)
- AのB(属性) ex) ティッシュ(A)の香り(B)
- AがBする(主述) ex) タクシー(A)が止まる(B)
- BをAする(動詞-目的語) ex) 客(B)を乗せる(A)
- AがあるからBがある(存在理由) ex) 猫(A)がいるから鳴き声(B)がある
この場合は全て以下のような形(Aの箱の中にBの箱が入る形)になります。
では概念リストよりさっそく作っていきましょう。
まず、【店名】と【店】ですがこれは、店の店名ですから…
となります(箱の大きさや位置は説明上の便宜的なのものです)。
続いて【電話番号】【住所】も追加していきましょう。店の電話番号、店の住所、
次に順番からいくと【日時】なのですが、これはすんなりと今のパズルに当てはめられないように思えます。これは大切な感覚です。「当てはめられない」は何か概念(実体)が足りないことを意味しています(もちろんそもそも当てはまらないこともありますが…)。ここは一旦スキップしましょう。
では、概念リストからすんなりと当てはめられそうなものを探すと…【店員】はどうでしょうか。
これはきっと以下のようになるでしょう。
続いて【商品】について考えていきたいのですが、まずはこの【商品】自身に入りそうな箱を考えてみましょう。
概念リストを見ると…【値段】【個数】はどうでしょうか。商品の値段、商品の個数…いけそうです。
こんな感じになるでしょうか。
さてこれはどこに入るでしょう。【店】の【商品】と考えられそうでしょうか?仮にそのように考えた場合、【店】の【商品】の【個数】とはどういう意味でしょうか?…言葉のままに捉えると「店頭に陳列された商品の個数」や「店の在庫商品の個数」という意味に聞こえます。本来の概念からズレてきていますね。
パズルの各概念は全て実体で考えるようにしましょう。この【商品】を商品マスターと捉えてしまうと、パズルは上手くつくれません(マスターはあくまで正規化した結果で定義される仮想の概念です)。では、このレシートが物語っている【商品】とは何なのでしょう。
【商品】が、レシートの背景にある「物を買う」という行為(ストーリー)の中で関係を持ったのはどの場面だったでしょうか…それは、【店員】が【販売】した場面(もしくは、【客】が【購入】した場面)ではないでしょうか?
従って【販売】という概念を追加して、パズルは以下のようになります。
足りなかった概念が追加されたことで、先ほど当てはめられなかった【日時】、また【総額】なども以下のように当てはめられそうです。
また、最後に残った【ポイント】についても、このままだと当てはめられないので概念を追加する必要があります。この【ポイント】は、【販売】した【客】の【ポイント】になると思われるので、
のように、【客】を追加すると上手く当てはめられました。
ここまでをまとめてみますと
- 概念のパズル化を行うことで概念同士の関係が可視化できる。
- 概念のパズルが上手くはまらない場合は、必要な概念が足りない可能性がある。
- 具体的に登場する場面をイメージしながら概念のパズルは全て実体で考えていく(マスターはあくまで正規化した結果で定義される仮想の概念)。
となります。
ERD(実体関連図)に展開
さてここからERDに展開していくのですが、ここは機械的です。例に上述のA・Bの概念を使うと以下のように展開できます。
- AがBを従属(AとBは親と子の関係)
- AとBは「1対多」の関係
これを、先ほど作った概念のパズル全体に適応すると以下のように展開できます。
さらに、何も従属していない概念
【店名】
【電話番号】
【住所】
【日時】
【総額】
【ポイント】
【個数】
【値段】
については、以下のようにその親の属性に含めても良いでしょう。
概念 ⇄ 属性は互いに垣根が曖昧で、どちらで定義をするかは設計者の裁量に任せらることが往々にしてあります。自身の場合は、目的のシステムにおいて、その概念が重要(別の概念を従属している等)であれば概念として、そうでない場合は属性として扱うことが多いです。
データベースERDへの変換と正規化
今度は、上のERDをデータベースの為のERDに書き換えます。外部キー(他の表に関連付けされたキー)を用いることでここも機械的にできます。
書き換え時に注意した点としては、サロゲートキー(代理主キー、意味を持たないIDフィールドを追加し主キーの代理をさせる)を全てのエンティティに置いたことてす。
サロゲートキーについては、是か否かの論争が巷でなされていますが、自身の個人的な見解としては「誰が見てもすぐに主キーだと判断できる」「主キー周りの仕様変更に強い」「最近のフレームワークを使った開発においてはデフォルト主キーがIDとなっている」の点から入れるようにしています。
正規化
ここで、上の5つのエンティティ(概念)は以下のように「リソース系」「イベント系」に分類できます。
-リソース系
店
店員
客
商品
-イベント系
販売
分類の方法としては、そのエンティティ内に「時間」的要素を含むかどうか(若しくは含ませられるかどうか)。含む場合は「イベント系」、含まない場合は「リソース系」として分類できます。今回は販売に日時が含まれるの、販売はイベント系に分類できます。
またここで説明しているリソース系のエンティティは、親にマスターのリソース系エンティティを持つことができ、これを行うことでデータの冗長性を防ぎます。冗長になり得るフィールドも一緒にマスターに移します。
今回は必要がないので触れませんが、必要があればその他正規化もこのタミングで行うと良いでしょう。
少しまとめますと
- 各エンティティにはサロゲートキーを置く
- 各エンティティはリソースとイベントに分類できる
- 概念のパズル化から導出されたリソース系エンティティにはマスターリソース系エンティティを親に追加できる(正規化)
- その他正規化もこのタイミングで行う
となります。
リレーションとエンティティの最適化
正規化したERDは、過程でありゴールではありません。目的のサービスの仕様や背景、また実際のデータの取り出し方を見ながらこれを最適化していきましょう。
先ずは、このサービスの仕様や背景を鑑みて既存ERDのリレーションが適切かどうか見ていきましょう。
リレーションを見る
既存のERDは以下のようなリレーションに直せるのではないでしょうか。
- 店マスター x 店:「店」は「店マスター」のうち、販売(というイベント)があった店と解釈できるため「 1 対 0 or 1」としました。
- 店員マスター x 店員:「店員」は「店員マスター」のうち、販売を行ったことがある店員と解釈できるため「 1 対 0 or 1」としました。
- 販売 x 客:1つの「販売」行為に対して「客」は一人になるため「 1 対 1」としました。
- 商品マスター x 商品:「商品」は実際に販売された商品であり、販売がなされていない商品も考えられるため「 1 対 0 or 多」としました。
ここで、少しリレーションというものを振り返ってみましょう。
リレーションのおさらい
① 1 対 1
左辺のレコードが決まると、右辺のレコードが1つ決まり必ず存在している状態。また、左辺のレコードが作られると同時に右辺も作られる必要があることを示しています。エンティティ同士の関連性が高い(片方だけでなく両方の情報を扱う場合が多い)場合は、パフォーマンスの観点からエンティティを1つまとめる方が良い場合もあります。
② 1 対 0 or 1
左辺のレコードが決まると、右辺のレコードが存在していない若しくは1つ決まる状態。また、左辺のレコードが作られた後に別のタイミングでもう右辺が作られることを示しています。エンティティ同士の関連性が高い(片方だけでなく両方の情報を扱う場合が多い)場合は、パフォーマンスの観点からエンティティを1つまとめる方が良い場合もあります。
③ 1 対 多
左辺のレコードが決まると、右辺のレコードが必ず1つ以上紐づいている状態。左辺のレコードが作成されると同時に、右辺の既存レコードと紐づくか、左記のようなレコードがない場合は右辺にも作られる必要があることを示しています。
④ 1 対 0 or 多
左辺のレコードが決まると、右辺のレコードが存在していない若しくは1つ以上紐づいている状態。
リレーションをしっかり作っておくと、データの作られる順番なども把握できるので、第三者でもこれを使った実装がイメージし易くなります。
さて、引き続きエンティティを見ていきましょう。
エンティティを見る
上述によりエンティティは以下のようにまとめられるのではないでしょうか。
- 店マスター x 店:「 1 対 0 or 1」の関係なので1つにまとめました。
- 店員マスター x 店員:「 1 対 0 or 1」の関係なので1つににまとめました。その際に「店員マスター」と「販売」のリレーションも見直しています。
- 販売 x 客:「 1 対 1」の関係なので1つにまとめました。結果的に「販売」が「店員マスター」と「客マスター」の中間テーブルになっていることがわかります。
- 商品エンティティの物理名・論理名を、より分かりやすく「sale_products」「販売商品」としました。結果的に「販売商品」が「販売」と「商品マスター」の中間テーブルになっていることがわかります。
今回の目的のシステムは「ある特定日のレシート一覧を出力できるシステム」です。特定日を指定するとレシート一覧が出力されるイメージです。データの取り出し方としては、特定日なる「販売」エンティティの「日時(date)」を指定し、それに紐づく周りのテーブルから情報を引っ張ってくることにります。
ですので、リレーション関係が遠いもの、例えば「店マスター」「販売」については以下のような紐付けを追加しても良いと思います。
これで一連のデータモデリング(概念設計〜論理設計前半)が完了しました。
まとめてみますと
- 正規化したERDは過程でありゴールではないため、最終的には使いやすい形に最適化していく(非正規化)。
- リレーションをしっかり考えることでデータの作られる順番なども把握できるので、第三者でもこれを使った実装がイメージし易くなる。
- リレーションをしっかり考えることでエンティティ同士をまとめることが可能になりシンプルなデータモデリングにつながる。
- 実運用に則してリレーション関係が遠いものについてリレーションを張ることも大切(非正規化)。
となります。
最後に
DB界権威のクリス・デイト氏自ら『データベース設計はきわめて主観的である』と認めざるを得ないほど、この分野は未だ曖昧な部分が多いです。
少し前に勉強がてら「概念設計」について調べてみたのですが、その方法や考え方が示されたドキュメントやサイトが少ない事に気づきました。言葉の説明については記載があるのですが肝心のフローや手法というものが見当たらないこともこれを物語っているかのようです。
ただ今回、特に「概念設計」について一つの考え方の種を示したく筆を起こしました。まだまだ整理しきれていない部分もあるかと思いますが、少しでも皆さんのデータモデリングのお役に立てれば幸いです。
ご清覧頂きありがとうございました。
Wedding Parkでは一緒に技術のウエディングパークを創っていくエンジニアを募集しています。
興味のある方はぜひ一度気軽にオフィスに遊びにきてください。
◉筆者のおすすめ記事
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第1回)
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第2回)
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第3回)
・やってみよう!AWSでWEBサーバー環境構築(Lambda|API Gateway|シリーズ第4回)
・【機械学習入門|Python|scikit-learn】結局何ができる?cheat-sheetから解説してみる篇