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

198 views

Published on

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

Published in: Software
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
198
On SlideShare
0
From Embeds
0
Number of Embeds
46
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

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

  1. 1. Datastore/Go のデータ設計と struct の振る舞いについて
  2. 2. 自己紹介 twitter : pospome blog :pospomeのプログラミング日記 職種 : サーバサイドエンジニア 興味  : クラス設計全般, DDD アイコン:羊じゃなくてポメラニアン その他 :「ポメ」って呼んでください。
  3. 3. 今日の発表の結論を言うと Datastoreのデータ構造の設計やレビューでは Kindに持たせる値だけではなく、 値が持つ振る舞いと特性も一緒に考えた方がいい
  4. 4. ソーシャルゲームのユーザー情報を表現する User Kind を 例に説明します
  5. 5. 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
  6. 6. RDBで考えるとこの構造で問題ないかもしれないが Datastore で適切とは限らない Datastore のデータ構造をマッピングした struct の 振る舞いを通して考えてみる
  7. 7. 例1 ProfileImage と ProfileMovie は どちらか一方しか登録できない という仕様を表現する
  8. 8. 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
  9. 9. type User struct { //他のフィールドは省略 ProfileImage, ProfileMovie string } それぞれが単なるフィールドだと 「Image, Movie どちらか一方を登録する」 という仕様を表現するのは難しい
  10. 10. これを修正すると・・・
  11. 11. 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 自体に持たせることで 仕様を表現することができる
  12. 12. 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
  13. 13. 例2 Email, TEL は OAuth Scope = Contact でしか取得できない という仕様を表現する
  14. 14. 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
  15. 15. 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 を表現している
  16. 16. これを修正すると・・・
  17. 17. 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 = “” のように抽象度がマッチしない場合 に比べると変更に強くなる
  18. 18. 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
  19. 19. 例3 ATK, DEF は HP の値によって増減する という仕様を表現する
  20. 20. 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
  21. 21. 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 に依存しているが、 他の値には依存していない
  22. 22. これを修正すると・・・
  23. 23. 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 を汚さずに「対戦」を表現できる
  24. 24. 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
  25. 25. ということで、最終的に・・・
  26. 26. 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 は以下になる
  27. 27. 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 は以下になるので、 最初のデータ構造とは違うものになった
  28. 28. まとめ
  29. 29. Datastoreのデータ構造の設計やレビューでは Kindに持たせる値だけではなく、 値が持つ振る舞いと特性も一緒に考えた方がいい
  30. 30. RDBだと struct の構造を そのまま保存するものではないので 必要に応じてORMや手動マッピングロジックで 永続化データとモデルをマッピングする struct の振る舞いを考慮してテーブル構造を 考える必要性は低い 永続化データとしての正しさを考えればいい RDBはSQLによる柔軟なクエリが可能なので、 無理やり struct を保存する工夫をするよりも 永続化データとしての正しさを重視した方がいい
  31. 31. Datastore は struct をそのまま保存できるので、 データ設計の段階で struct を考慮して設計すると 手戻りが少なく、 自分でインピーダンスミスマッチを解消する必要もない 極端に言うと Datastore設計 = モデル設計 ただし、 Datastore に保存できないデータ構造もあるので注意
  32. 32. Kind のプロパティ名に Prefix がある場合は その値は関連性の高い値である可能性が高い 関連性の高い値は それ独自の振る舞いや特性を持つ可能性が高い そういった関連性の高いデータに対して 仕様を表現するロジックを紐付けることによって、 責務が明確になる
  33. 33. 今回の例は説明用ということもあって、 結構無理矢理なケースかと思います 今回の例であれば Datastore のプロパティを フラットに並べても問題ないかもしれません ここはモデルの設計方針によって変わります 重要なのは 「struct の振る舞いも考慮する」 という選択肢を持つことです
  34. 34. Datastore の設計をする際には struct の振る舞いも考慮してみてはいかがでしょうか?
  35. 35. おわり

×