LaravelのDB構造をリファクタしてみた。
Date: 2022/06/18 18:46
Category: none
はじめに
副業で2年くらい前にLaravelで作ったWebサイトがある。本当はLaravelを使う必要はほとんどないのだが、わがままを言ってLaravelで作成させていただいた。そのサイト専用のCMSを作ると言う点ではなかなか良い勉強になった。
ただし、その頃はLaravelを勉強しながらサイトを作っていたし、実業務でもあまりテーブル設計やテストコードの実装などをやっていなかったのもあり、メンテナンスの観点ではまだまだ未熟なものである。
たとえば、今回は全体的にテストコードを拡充し、Laravelのバージョンアップの準備を進めようと思っていたのだが、そこにあったのはテストを書くのも一苦労するようなControllerに処理が直書きされているようなコードだった。
このControllerのテストを書きながら、Ajaxで記事をリクエストする処理がイケてないことに気付きリファクタを始めた。(どの記事を見ながら書いたのか忘れたが、ControllerでURLを生成し、それをテンプレートに埋め込まれたjsへ渡し、再検索をかけるためajaxエンドポイントにgetでリクエストしているようなコードだ... 自分で書いておきながら恐ろしいコードである...)
そのように非同期での記事検索を本来あるべきajaxでの実装に置き換えたとき、既存の機能のうち動かない部分を見つけた。そのサイトの記事にはキーワードを表すタグが付いているのだが、そのタグの付いている記事を正しく検索できなくなっていたのだ。
そのサイトではタグの文字列を用いて検索するようにしていたが、POSTエンドポイントとはいえど入力される値の形式は整えておきたいとのことでそのタグを管理するIDで検索するように変更していた。だがどうやらそのタグのIDが正しく取得できていないと言うことが判明し、はじめてDBをみることにしたのだった...
table:tags
| id | name | column_id | created_at | updated_at |
|---|---|---|---|---|
| 1 | タグ1 | 1 | ||
| 2 | タグ1 | 2 | ||
| 3 | タグ2 | 2 | ||
| 4 | タグ3 | 2 |
上記のようなテーブル構造をしていた。おそらく作成当初はタグを使う場所なんてあんまりないし、完全な関係テーブルにする必要はないと判断したのかもしれない... (それでも完全な関係テーブルにしておくべきだっただろう...)
このテーブルにおいてIDはnameとcolumn_idのペアに対するものであり、本来のtagに対してのものではない。そのため、nameではなくidで単純に検索をかけると、同じタグ文字列なのに別なIDでヒットするというものになってしまっている。タグ一覧の画面で3つのタグだけが表示されることを期待したのに、nameが同じでもidが異なるので4つのタグが表示されるという間抜けな状態になってしまった。
そこでこの問題を解消するために、tagsとcolumnsという2つのテーブルの関係テーブル tag_columnsに分割することにした。
やること
中途半端に実装されてしまった関係テーブルもどきを以下のように完全な関係テーブルにする。
table:tags
| id | name | created_at | updated_at |
|---|---|---|---|
| 1 | タグ1 | ||
| 2 | タグ2 | ||
| 3 | タグ3 |
table:tag_columns
| id | tag_id | column_id | created_at | updated_at |
|---|---|---|---|---|
| 1 | 1 | 1 | ||
| 2 | 1 | 2 | ||
| 3 | 2 | 2 | ||
| 4 | 3 | 2 |
やったこと
まず元のテーブルをリネームし、完全に参照用のテーブルにした。
新しくtags(タグの情報しか持ってない本来の意味のtags)とtagsとcolumnsの関係だけを定義した関係テーブル tag_columnsをそれぞれ定義する。(SequelProで新しいテーブルを作成しカラムを追加してから、TableInfoをコピペしたものが以下のもの)
もともとのテーブル(tags_bkにリネームされている)からtagsに入るべき情報だけを引っ張ってきてINSERTする。
関係テーブルも同様にtags_bkとtagsから入るはずの値を検索してきてINSERTした。
最後にもう不要となったもとの中途半端な関係テーブルもどきを削除すれば完成。
ここまでの流れをlaravelのmigrationでやる。
その前にtags, tag_columnsから tags_bkを復元するクエリも書き出しておく。(rollbackができるように)
あとはひたすら laravelのmigrationファイルにこれらを書いていけばいい。
laravelでテーブルをrenameする時は、Schema::rename(from, to);を使えばできる。
新しく作る tags, tag_columnsの定義に関しては以下のように定義してやればよい。
upのところでcreate文を書き、down(rollback時の処理)ではテーブルを削除(drop)している。
tagsやtag_columnsに値を流し込む作業もmigrationファイルに記述してしまって良いらしい。
tag_columnsの方では、tags_bkにtagsを INNER JOIN して取得した各項目を1行ずつforeachの中でINSERTする処理を書いている。これで既存データのお引っ越しは完了だ。
downの方ではtag_columnsをTRUNCATEしている。(tag_columnsに値がない状態にすればいいのでこれでOK)
最後にリネームしたtags_bkを削除(drop)すればOKである。
ただし、削除したものをrollbackするにはtags_bkを作ること、tags,tag_columnsから中身を作ってINSERTしてやることが必要になるので以下のように少し面倒だ。
というステップを踏んで、テーブル分割作業をSQLのクエリからmigrationファイルに書き起こすことができた。
まとめ
DBの構造がいけてないと後々の改修の際に苦労することになる。ただ、そんな時でも元のデータを保ちながらDB構造のリファクタをすることはできるようだ。
まず,クエリでどうすればリファクタできるかを考えてから、Laravelのmigrationファイルに書き起こす。migrationの中でデータの引越しも書いてしまえばいざと言う時にrollbackして元の構造に戻すこともできるし、変更したときの記録が残って良い。
というわけでテーブル構造のリファクタをしたのだが、少々面倒ではあったので、最初から適切にテーブルを設計しておくことの大切さを再認識させられた。
また、この変更によりLaravelのEloquentModelやロジックの修正は必要そうなのでおいおいやっていこうと思う。
まさき。です。PHPエンジニアをやってます。
自分の課題を技術で乗り越えるの好きかもしれないです。
フロントエンドは苦手ですが、少しでもできるようになれたらな、ということでNextJSでこのブログサイトを作りました。


