フルネームで保持しているカラムを後から「名字・名前」に分けたい時、どうすればいいか考えてみた。
出来たと思うけど、脳内シミュレーションしただけで実際にシステムに対して行ったことがあるわけではないので、もしかしたら穴があるかも。
サンプルケース
既存実装では「名字・名前」を分けてなかった
ユーザー情報を保持するuserテーブルがあって、名前を保持するnameカラムが定義されている。
userテーブル
データベースからデータを読み出して、Userクラスとして使っている
// Userクラス
class User {
id int
name string
construct(id int, name string){
user_id = id
name = name
}
function getName(){
return name
}
}
// dbから取得したデータをセット
user = new User(1, "山田 太郎")
// 名前取得
user.getName() // "山田 太郎"が取得できる
今後は「名字・名前」を分けて保持してほしいという要件が発生した
ある時、名字と名前を使い分けたいという要望が発生した為、名字と名前を分けて保持することにした。
ただし、既に登録されているユーザーの名前を変換することができない。
(空白部分で「名字・名前」を分けてデータをコンバートすればいいという案が出たが、「岡田次郎」のように空白を入力していないユーザーもいたため不可能だった)
どうすればいいか?
名前保持用のテーブルに分割する
旧名前用データテーブル(フルネームしか持ってない)と新名前用データテーブル(「名字・名前」データで持っている)を用意して使い分ける。
userテーブル
user_nameテーブル(旧名前用データテーブル。userテーブルからnameを移動させる)
user_id |
name |
1 |
山田 太郎 |
2 |
岡田次郎 |
user_separate_nameテーブル(新名前用データテーブル。以降はこのテーブルに名前データを入れていく)
user_id |
first_name |
last_name |
3 |
三郎 |
川上 |
データ取得時はjoinして取る
select
user.id,
user_name.name,
user_separate_name.first_name,
user_separate_name.last_name
from user
left outer join user_name on user_name.user_id = user.id
left outer join user_separate_name on user_separate_name.user_id = user.id
こんな感じで取れる
id |
name |
first_name |
last_name |
1 |
山田 太郎 |
null |
null |
2 |
岡田次郎 |
null |
null |
3 |
null |
三郎 |
川上 |
プログラム側で扱うデータ構造はこんな感じ
// インターフェース
interfacd IName{
getFullName() string
getFirstName() string
getLastName() string
}
// 旧名前データ用クラス。INameインターフェースを実装する
class UserName < IName {
user_id int
name string
construct(id int, name string){
user_id = id
name = name
}
function getFullName(){
return name
}
function getFirstName(){
return null
}
function getLastName(){
return null
}
}
// 新名前データ用クラス。INameインターフェースを実装する
class UserSeparateName < IName {
user_id int
first_name string
last_name string
construct(id int, first_name string, last_name string){
user_id = id
first_name= first_name
last_name = last_name
}
function getFullName(){
return last_name + " " + first_name
}
function getFirstName(){
return first_name
}
function getLastName(){
return last_name
}
}
// Userクラス。INameインターフェースを実装したクラス(旧 or 新名前用クラス)を内部に持って、名前情報取得処理を委譲する。
class User{
id int
name IName
construct(id int, name IName){
user_id = id
name= name
}
// getFullNameにメソッド名変更してもいいかも。
function getName(){
return name.getFullName()
}
function getFirstName(){
return name.first_name
}
function getLastName(){
return name.last_name
}
}
// 使う時こんな感じ
// user_nameテーブルのデータを読み込む
user_name = new UserName(1, "山田 太郎")
// userテーブルデータを読み込む
user = new User(1, user_name)
// 使う
user.getName() // 山田 太郎
user.getFirstName() // null (旧データなのでfirst_nameデータがない)
user.getLastName() // null (旧データなのでlast_nameデータがない)
// user_separate_nameテーブルデータを読み込む
user_separate_name = new UserSeparateName(3, "三郎", "川上")
// userテーブルデータを読み込む
user = new User(3, user_separate_name)
// 使う
user.getName() // 川上 三郎
user.getFirstName() // 三郎(新データなのでfirst_nameデータがある)
user.getLastName() // 川上(新データなのでlast_nameデータがある)
(他案)userテーブルにfirst_nameとlast_nameカラムを追加するだけでいいのでは?=> 良くない
テーブル分割してややこしくしなくても、first_name, last_nameカラムをuserテーブルに追加すれば良い気がしたりするけど、個人的にはNG。
↓こんな感じ?
id |
name |
first_name |
last_name |
1 |
山田 太郎 |
null |
null |
2 |
岡田次郎 |
null |
null |
3 |
川上 三郎 |
三郎 |
川上 |
※1,2が旧データ、3が新データ
なぜNGか?
データを冗長に保持している感じが嫌。
こんなデータを入れられてしまう可能性がある。
id |
name |
first_name |
last_name |
4 |
岡本 太郎 |
武蔵 |
宮本 |
この場合、フルネームを取得すると「岡本 太郎」が取得できるけど、名字を取得すると「宮本」となる。
データ不整合が発生する可能性のあるデータ構造は、アプリケーション側やオペレーター側に整合性担保の責任を押し付けてくるので良くないと思ってる。