ブログ
【Laravel / DB設計】イケてないやり方(アンチパターン)に気をつけよう
今回は私M.YがLaravelでのDB設計時にやらかしてしまったお話を書きたいと思います。
アンチパターンの例
前回のブログ でも少し触れたのですが、前職ではフレームワークを使用していない素のPHPで実装を行なっていました。
そのため必然的にDBからデータを取得する際も素のSQLを直接文字列で書くことになります。
その経験が生んでしまったDB設計がこちら💁(絶対に真似してはいけません⚠️)
テーブルの概要
このテーブル(= classification_table) は、同じ構造で且つマスタを持つ必要がないもの(データが固定なもの、など)をなんでもポンポン入れてしまった、ごった煮状態テーブル💧になっていました。
ID | name | group |
---|---|---|
1 | 大区分 | categories |
2 | 中区分 | categories |
3 | 小区分 | categories |
4 | 紹介 | contacts |
5 | メール | contacts |
6 | 広告 | contacts |
7 | 北海道 | areas |
8 | 東北 | areas |
9 | 関東 | areas |
10 | 中部 | areas |
11 | 近畿 | areas |
12 | 中国 | areas |
13 | 四国 | areas |
14 | 九州 | areas |
区分(ここではgroup)の値で取得したものを配列(あるいはオブジェクト)でもち、その値の中から必要なデータ(ここではname)を他のテーブルに紐づける、と言ったイメージ。
実際、classification_table をどう利用するかというと、以下の user_table のようにもたせていました。
(さらに user_table 以外のテーブルでも classification_table が参照されるような設計になってしまっていましたが、今回は省略します)
ID | name | category_id | contact_id | area_id |
---|---|---|---|---|
1 | hoge | 1 | 4 | 9 |
2 | fuga | 3 | 5 | 14 |
3 | piyo | 2 | 6 | 9 |
user_table.category_id を設定する際は classification_table.group = categories のものから設定する、という(私の中では)よくありそうな設計でした。(user_table.contact_id, user_table. area_id でも同様)
DB設計に関する疑問
ある日、PJメンバーからとある疑問をいただきました。
『このclassificationテーブルのデータって、データベースに格納して持つ必要あるんでしょうか👀』
テーブルで持つ以外だったらどうしましょうか🤔と相談させていただき、
”各モデルに定数で持たせれば良いのでは?💡”
と、例えば以下のようにしてはいかがでしょうか。とアドバイスをいただきました🙇♀️
// 定義の例
public const CATEGORY_LABEL = [
self::DAI => '大区分',
self::CHU => '中区分',
self::SHOU => '小区分'
];
確かに、マスタデータとしてシステム側でのCRUDを準備するほどではないデータだからと、わざわざなんでもかんでもDBに持たせる必要はそもそもありませんでした。
Laravelではアクセサや前回のブログでも紹介したscopeを利用し、以下のように定義・取得することができます💡
// 取得するときの例
public function getCategoryLabelAttribute(): string
{
return self::CATEGORY_LABEL[$this->category];
}
public function scopeCategoryDAI($query)
{
return $query->where(function ($builder) {
$builder->orWhere('categories', '=', self::DAI)
});
}
なぜこんな設計をしてしまったのか
さらに、なぜテーブルにわざわざ持たせてたんだろうか?🤔というところにまで話は及びます。
その理由はこのブログの前半に書いた『DBからデータを取得する際も素のSQLを直接文字列で書く』という、自分の中での勝手な常識となってしまっていた、まさにヨロシクナイ習慣 (= アンチパターン)でした😨
(Laravelで言うところの)Modelを意識していなかったため、直接SQLそのものをガリガリ書いても違和感を感じていなかったのです。
フレームワークがちゃんと準備してくれています
そもそも、現在携わっている開発で使用しているLaravelでは、素のSQLを書くことは基本的にありません。(前回のブログ参照)
Laravelにはとても便利な ORM(Eloquent) が組み込まれています✨
Eloquentはデータベースとモデルを対応づける機能です。
ちゃんとEloquentモデルオブジェクトで返してくれてデータ型も担保されている上に、その後のデータの取り扱いを便利にしてくれるクエリビルダも使うことができます👍
しかし、今回のようなごった煮テーブルとなってしまった classification_table でEloquentを使うことの旨味はおそらくほぼなさそうです🤷♀️
しかもなんのデータが入ってるのかもぱっと見では分かりづらく、メンテナンス性も悪くなりそうです🙅♀️
(なんなら格納する値の型が異なった場合、 classification_table では格納することができずまた似たような構造のごった煮テーブルを作るようなことになりかねない😫)
今回の出来事で得た教訓
- 何が入っているかぱっと見ではわからないようなテーブルは作るべからず
- 何でもかんでもデータをDBに格納すればいいというものではない
- フレームワークの仕様に準拠したDB設計を意識すること
そんなこんなで久しぶりに アンチパターン本 と向き合おうと思いました・・・・😵💫
今回のブログを執筆するにあたりとても参考にさせていただきました
参考サイト:Laravel 8.x Eloquentの準備
公式ドキュメントはちゃんと読まなきゃいけないなといつも反省
参考書:SQLアンチパターン
必読本の一つです!
株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!