GORM には embedded というタグが用意されており、値オブジェクト(Value Object)チックな構造体を他の構造体に簡単に埋め込むことができそうなので、メモ。
gorm.io
値オブジェクト
値オブジェクトとは、住所情報のように、不変かつ交換可能なオブジェクトです。
"港区 六本木 1-1-x" という同じ値オブジェクトが 2 つ存在する場合、その 2 つは同等である必要があります。
今回は住所情報を持つ値オブジェクトチックな構造体を表現する Address Struct を用意し、他の構造体に埋め込む実装を行います。
"チック" と表現しているのは、今回の実装では値の再代入を禁止する方法がないため、完全な値オブジェクトと言えないと判断したためです。
Address Struct
住所情報を持つ Address Struct を用意する。Address Struct は Street (住所の通り情報) と City (住所の都市情報) を内部で持ち、それらを組み合せて使うメソッドが用意しています。
package valueobject
type Address struct {
Street string
City string
}
func (a *Address) IsClose(address Address) bool {
return a.City == address.City
}
func (a *Address) ToString() string {
return a.Street + " " + a.City
}
Customer Struct
実際に値オブジェクトを埋め込ませる構造体として Customer Struct を用意します。Customer Struct は Address (住所情報) を持つ構造体です。
ここで GORM の embedded タグを使って Address Struct を埋め込みます。
embeddedPrefix はカラム名のプレフィックスを省略するのに使います。
package entity
import (
vo "github.com/tabakazu/value_object_sample/valueobject"
)
type Customer struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"column:name"`
Address vo.Address `gorm:"embedded;embeddedPrefix:address_"`
}
DB と連携
実際に実装した Struct と DB のデータをマッピングしてみます。
事前に下記のテーブル、データを用意します。
CREATE TABLE `customers` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COMMENT '名前',
`address_street` varchar(255) COMMENT '住所 通り',
`address_city` varchar(255) COMMENT '住所 都市',
`created_at` datetime(6) NOT NULL,
`updated_at` datetime(6) NOT NULL,
`deleted_at` datetime(6) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `customers` (name,address_street,address_city,created_at,updated_at))
VALUES ("John Snow", "Roppongi 1-1-x", "Minato-ku", NOW(), NOW());
実際に DB からデータを取得します。埋め込んだ値オブジェクトも正しくマッピングされており、定義した各メソッドを呼び出せます。
var cus entity.Customer
db, _ := gorm.Open(mysql.Open(os.Getenv("MYSQL_URL")), &gorm.Config{})
db.First(&cus, "name = ?", "John Snow")
cus.Address.City
otherAddress := valueobject.Address{City: "Minato-ku"}
cus.Address.IsClose(otherAddress)
otherAddress := valueobject.Address{City: "Shibuya-ku"}
cus.Address.IsClose(otherAddress)
cus.Address.ToString()
otherAddress := vo.Address{City: "Minato-ku", Street: "Roppongi 1-1-x"}
cus.Address == otherAddress
otherAddress := vo.Address{City: "Minato-ku", Street: "Roppongi 1-1-y"}
cus.Address == otherAddress
データ挿入時も問題なく動作します。
cus := entity.Customer{
Username: "Tyrion Lannister",
Address: vo.Address{
City: "Minato-ku",
Street: "Roppongi 1-1-x",
},
}
db, _ := gorm.Open(mysql.Open(os.Getenv("MYSQL_URL")), &gorm.Config{})
if err := db.Create(&cus).Error; err != nil {
fmt.Println(err)
} else {
fmt.Println("Success")
}
良き値オブジェクトを発見した時は是非使いたい機能でした。
以上です。