狠狠撸

狠狠撸Share a Scribd company logo
Datastore/Go のデータ設計と
struct の振る舞いについて
自己紹介
twitter : pospome
blog :pospomeのプログラミング日記
職種 : サーバサイドエンジニア
興味  : クラス設計全般, DDD
アイコン:羊じゃなくてポメラニアン
その他 :「ポメ」って呼んでください。
今日の発表の結論を言うと
Datastoreのデータ構造の設計やレビューでは
Kindに持たせる値だけではなく、
値が持つ振る舞いと特性も一緒に考えた方がいい
ソーシャルゲームのユーザー情報を表現する User Kind を
例に説明します
ID TEL Email
Profile
Image
Profile
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
RDBで考えるとこの構造で問題ないかもしれないが
Datastore で適切とは限らない
Datastore のデータ構造をマッピングした struct の
振る舞いを通して考えてみる
例1
ProfileImage と ProfileMovie は
どちらか一方しか登録できない
という仕様を表現する
ID TEL Email
Profile
Image
Profile
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
type User struct {
//他のフィールドは省略
ProfileImage, ProfileMovie string
}
それぞれが単なるフィールドだと
「Image, Movie どちらか一方を登録する」
という仕様を表現するのは難しい
これを修正すると???
type User struct {
Profile Profile
}
type Profile struct {
Image, Movie string
}
func NewImageProfile(image string) Profile {
return Profile{
Image: image,
}
}
func NewMovieProfile(movie string) Profile {
return Profile{
Movie: movie,
}
}
「Image, Movie どちらか一方を登録する」
というルールを Profile 自体に持たせることで
仕様を表現することができる
ID TEL Email
Profile.
Image
Profile.
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
例2
Email, TEL は
OAuth Scope = Contact でしか取得できない
という仕様を表現する
ID TEL Email
Profile.
Image
Profile.
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
type User struct {
TEL, Email string
}
func Get(scope string) User {
var u User = GetUserFromDB()
if scope != "Contact" {
u.TEL = ""
u.Email = ""
}
return u
}
ロジック上で Contact scope = Email, TEL を表現している
これを修正すると???
type User struct {
Contact Contact
}
type Contact struct {
TEL, Email string
}
func Get(scope string) User {
var u User = GetUserFromDB()
if scope != "Contact" {
u.Contact = nil
}
return u
}
Scope が扱う Contact という概念は
具体的に TEL, Email を含む
抽象度の違う値同士を扱おうとすると、
ロジックが複雑になる可能性がある
TEL, Email という具体的な概念を
Scope の Contact という抽象度に合わせる
Scope と struct が一致しているので、
直感的に理解しやすい
Contact の持つ値が変化しても、
u.Contact = nil に修正は発生しない
これはロジックが Contact という抽象度の概念
を扱っているからであって、
t.TEL = “” のように抽象度がマッチしない場合
に比べると変更に強くなる
ID
Contact
.TEL
Contact
.Email
Profile.
Image
Profile.
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
例3
ATK, DEF は HP の値によって増減する
という仕様を表現する
ID
Contact
.TEL
Contact
.Email
Profile.
Image
Profile.
Movie
HP ATK DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
type User struct {
HP, ATK, DEF int
}
func (u *User) GetAtk() int {
//HPに依存する
return u.ATK * u.HP
}
func (u *User) GetDef() int {
if u.HP < 100 {
//ピンチになると強くなる
return u.DEF * 2
}
return u.DEF
}
それぞれの値を算出するロジックは
HP, ATK, DEF に依存しているが、
他の値には依存していない
これを修正すると???
type User struct {
Battle Battle
}
type Battle struct {
HP, ATK, DEF int
}
func (b *Battle) GetAtk() int {
//HPに依存する
return b.ATK * b.HP
}
func (b *Battle) GetDef() int {
if b.HP < 100 {
//ピンチになると強くなる
return b.DEF * 2
}
return b.DEF
}
HP, ATK, DEF を Battle として定義
「対戦」に関するロジックは
Battle に集中させる
User は「対戦」以外のロジックに集中できる
対戦のロジックはゲームのコアな要素なので、
複雑な仕様になりやすい
User から分離しておくと
Battle に interface を持たせて
特定のロジックを抽象化させたり、
固定値を設定した Battle に差し替えるなど、
User を汚さずに「対戦」を表現できる
ID
Contact
.TEL
Contact
.Email
Profile.
Image
Profile.
Movie
Battle.
HP
Battle.A
TK
Battle.
DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
ということで、最终的に???
type User struct {
ID int64
Contact Contact
Profile Profile
Battle Battle
}
type Contact struct {
TEL, Email string
}
type Profile struct {
Image, Movie string
}
type Battle struct {
HP, ATK, DEF int
}
振る舞いを考慮すると User struct は以下になる
ID
Contact
.TEL
Contact
.Email
Profile.
Image
Profile.
Movie
Battle.
HP
Battle.A
TK
Battle.
DEF
1 000 x x.png 100 100 100
2 111 y y.mp4 50 50 50
3 222 z z.png 200 200 200
User struct を保存する User Kind は以下になるので、
最初のデータ構造とは違うものになった
まとめ
Datastoreのデータ構造の設計やレビューでは
Kindに持たせる値だけではなく、
値が持つ振る舞いと特性も一緒に考えた方がいい
RDBだと struct の構造を
そのまま保存するものではないので
必要に応じてORMや手動マッピングロジックで
永続化データとモデルをマッピングする
struct の振る舞いを考慮してテーブル構造を
考える必要性は低い
永続化データとしての正しさを考えればいい
RDBはSQLによる柔軟なクエリが可能なので、
無理やり struct を保存する工夫をするよりも
永続化データとしての正しさを重視した方がいい
Datastore は struct をそのまま保存できるので、
データ設計の段階で struct を考慮して設計すると
手戻りが少なく、
自分でインピーダンスミスマッチを解消する必要もない
極端に言うと
Datastore設計 = モデル設計
ただし、
Datastore に保存できないデータ構造もあるので注意
Kind のプロパティ名に Prefix がある場合は
その値は関連性の高い値である可能性が高い
関連性の高い値は
それ独自の振る舞いや特性を持つ可能性が高い
そういった関連性の高いデータに対して
仕様を表現するロジックを紐付けることによって、
責務が明確になる
今回の例は説明用ということもあって、
結構無理矢理なケースかと思います
今回の例であれば
Datastore のプロパティを
フラットに並べても問題ないかもしれません
ここはモデルの設計方針によって変わります
重要なのは
「struct の振る舞いも考慮する」
という選択肢を持つことです
Datastore の設計をする際には
struct の振る舞いも考慮してみてはいかがでしょうか?
おわり

More Related Content

Datastore/Go のデータ設計と struct の振る舞いについて