浜松のWEBシステム開発・スマートフォンアプリ開発・RTK-GNSS関連の開発はお任せください
株式会社シーポイントラボ
TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:静岡県浜松市中区富塚町1933-1 佐鳴湖パークタウンサウス2F

データベースから離れたモデリングをする

 データベースはなるべく正規化をすべき、モデルはなるべく現実の業務に即すべき、という方針で作られることが多いです。少なくとも自分はそういう風によく設計します。もちろんそれぞれそれのみがあれば他がどうでもいいわけではありません。特にデータベースのリレーションはある情報を得るためにまたがるテーブルを減らせる様に気にしています。データベースとモデルはそれぞれ理想とする状態が異なります。このため、データベースをマッピングするORMが理想のモデル構築になることは滅多にありません。
 例えば、次のQiitaの記事があります。
役割駆動設計で巨大クラスを爆殺する – Qiita
 この記事は、様々なことを行うユーザをモデリングするならば、ユーザの行動やユーザのおかれている状況を役割として役割別にモデリングすべき、という記事です。この役割駆動設計に従うとユーザとそのユーザの役割についての情報を持つモデルが作られます。上記記事から引用したモデルクラスが次になります。

 ユーザを役割で分割したため、当然ですが「者」、「人」とクラス名がついています。また、共通項として識別IDを持ちます。このモデルクラスらはシンプルですが、web上で表示すること、APIとして返すことを考えると共通項にはユーザ名などいくつか基本情報を持つことになります。このモデリングを愚直にテーブルに反映すると基本情報の重複に依て正規化は為されません。正規化されたテーブルからこのモデルクラスを構築するには次の様なjoinないしリレーションが必要になります。

# 会員基本情報 : 会員発送情報 = 1 : 0..n
# JOINは早いがJOIN句を使えない命令もある。そういう時はトランザクションを使う
# 重複情報はプログラム側で落とすのが簡易。{ 基本情報, [発送情報 x n]} 的な構造体を作成する
SELECT 会員基本情報.*, 会員発送情報.*,
    FROM 会員基本情報
        LEFT JOIN 会員発送情報
# リレーションは大体こんな感じでクエリが増えて遅くなりがち
SELECT * FROM 会員基本情報
SELECT * FROM 会員発送情報 WHERE 会員ID = 会員基本情報.ID

 ここからORMの1クラスに対して1テーブルという特徴では役割駆動設計を直接的に表現し難いことがわかります(おそらくJOIN句を使えない命令がある絡みが原因?)。
 一方でフレームワークの持つORM機能はとんでもなく便利です。Laravelに備わっているEloquentなどはそれだけでセキュアで高速なCRUDを隠蔽して備えています。それなりに巨大なシステムを組む段階になってようやくfat model問題が見え始めるくらいです。
 ORMを利用して役割駆動設計的モデリングができるとありがたいです。そこで出てくるのがリポジトリパターンです。Microsoftはリポジトリパターンを次のように説明しています。

リポジトリは、データソースへのアクセスに必要なロジックをカプセル化するクラスまたはコンポーネントです。
インフラストラクチャの永続レイヤーの設計 | Microsoft Docs

 要は上記のORMで表現しきれないデータのアクセスをリポジトリクラスとしてカプセル化します。これを利用して次のクラス図、ER図もどきの様にすると、Eloquentを扱って楽にクエリを作り、クエリを元に扱いやすいモデルクラスを制御するリポジトリクラスを用意できます。こうするとORMの利点とプロジェクトの解決すべき問題に即したモデリングの利点の両方を受け取れます。

 余談ですが、ORMと異なるものを作っているからこそリポジトリが有効です。ORMクラスを素通りさせるだけのリポジトリパターンはクラスが無意味に増えてコードを読む時間がただ増えるのみです。

  • この記事いいね! (0)