Pro Yearly is on sale from $80 to $50! »

Implementing Go Go Go

E48409e5c7d5b8919b03d3d3c4a40da7?s=47 azihsoyn
October 02, 2016

Implementing Go Go Go

Implementing Domain-Driven Design in golang

https://github.com/azihsoyn/IDDD_go_sample

E48409e5c7d5b8919b03d3d3c4a40da7?s=128

azihsoyn

October 02, 2016
Tweet

Transcript

  1. ࣮ફGoGoGo Gunosy Inc. 2016.10

  2. ©Gunosy Inc. 2 ࣗݾ঺հ ▪ azihsoyn (͋͡;ͦ΍Μ) – Twitter –

    Github – …etc ▪ ۀ຿ – GoͰAPIαʔόͷ։ൃɾӡ༻ ▪ झຯ – ௼Γ(6ׂ) • ઒΍ւͰϧΞʔ௼Γ – Ξχϝ(4ׂ) • μΠϠ༷ʙ ▪ ۙگ – isucon6ຊઓग़·͢ʂ
 (νʔϜ: ͝஫จ͸poyoͰ͔͢ʁ) ٢ᖒ ௚࠸
  3. ©Gunosy Inc. 3 Gunosy͸ ৘ใΩϡϨʔγϣϯαʔϏεʮά ϊγʔʯͱ 2016೥6݄1೔ʹKDDIגࣜձࣾͱڞಉͰϦϦʔεͨ͠
 ແྉχϡʔε഑৴ΞϓϦʮχϡʔεύεʯΛఏڙ͢Δ ձࣾͰ͢ɻʮ৘ใΛੈքதͷਓʹ࠷దʹಧ͚ΔʯΛ
 Ϗδϣϯʹ׆ಈ͍ͯ͠·͢ɻ

    ωοτ্ʹଘࡏ͢Δ͞·͟·ͳ৘ใΛɺ
 ಠࣗͷΞϧΰϦζϜͰऩूɺධՁ෇͚Λߦ͍
 Ϣʔβʔʹಧ͚·͢ɻ ৘ใΩϡϨʔγϣϯαʔϏε
 ʮά ϊγʔʯ 200ഔମҎ্ͷχϡʔειʔεΛϕʔεʹɺ
 ৽ͨʹ։ൃͨ͠৘ใղੳɾ഑৴ٕज़Λ༻͍ͯࣗಈతʹ
 બఆͨ͠χϡʔε΍৘ใΛϢʔβʔʹಧ͚·͢ɻ ແྉχϡʔε഑৴ΞϓϦ
 ʮχϡʔεύεʯ גࣜձࣾGunosy – ʮ৘ใΛੈքதͷਓʹ࠷దʹಧ͚Δʯ
  4. ©Gunosy Inc. 4 GoͰDDD΍ͬͯΔ࿩ ࠓ೔ͷ࿩

  5. ©Gunosy Inc. 5 ࣮ફDDDಡΈऴΘͬͯͳ͍Ͱ͢ DDDͷํ͸ಡΜͩΜͰ͕͢… લఏ

  6. ©Gunosy Inc. 6 ▪ https://github.com/citerus/dddsample-core – DDDͷαϯϓϧ࣮૷(Java) ▪ https://github.com/VaughnVernon/IDDD_Samples –

    ࣮ફDDDͷαϯϓϧ࣮૷(Java) ▪ https://github.com/marcusolsson/goddd – DDDͷαϯϓϧ࣮૷(golang) ͜ͷ͋ͨΓͷίʔυಡΈͳ͕ΒϓϩμΫτʹམͱ͜͠Μͩײ͡Ͱ͢
  7. ©Gunosy Inc. 7 ࣝऀͷํʑ͔ΒͷಥͬࠐΈେ׻ܴͰ͢

  8. ©Gunosy Inc. 8 վΊͯࠓ೔ͷ࿩ ࿩͢͜ͱ ࿩͞ͳ͍͜ͱ ▪ GoͰAPIॻ͘ͱ͖ʹؾΛ͚ͭͯΔ͜ͱ ▪ ύοέʔδߏ੒

    ▪ ϋϚͬͨ͜ͱ ▪ DDDͷ༻ޠͷઆ໌ͱ͔ ▪ ϥΠϒϥϦͷ࢖͍ํͱ͔ GoͰDDDΛ΍ͬͯࢼߦࡨޡͨ͠࿩Λ͍ͨ͠ͱࢥ͍·͢
  9. ©Gunosy Inc. 9 هࣄΛฦ͢APIΛ࡞Δ৔߹Λ૝ఆͯ͠ΈΔ

  10. ©Gunosy Inc. 10 ▪ main.goΛॻ͘ ▪ handlerΛॻ͘ ▪ هࣄͷmodelΛॻ͘ ▪

    هࣄͷmodelΛσʔλετΞ͔Βऔಘ͢ΔॲཧΛ࣮૷ ▪ jsonͱ͔ʹม׵ͯ͠ฦ͢ େମ͜ΜͳྲྀΕʹͳΔͱࢥ͍·͢
  11. ©Gunosy Inc. 11 ·ͣ͸main.go

  12. ©Gunosy Inc. 12 // main.go package main import ( "context"

    "github.com/azihsoyn/IDDD_go_sample/article" "github.com/azihsoyn/IDDD_go_sample/internal/repository" "github.com/guregu/kami" ) func main() { ctx := context.Background() ctx = repository.NewContext(ctx) kami.Context = ctx kami.Get("/articles", article.ArticleListHandler) kami.Get("/article/:article_id", article.ArticleHandler) kami.Serve() } repositoryͱ͍͏ͷΛNewͯ͠ΔҎ֎ಛʹมΘͬͨ͜ͱ͸ͯ͠ͳ͍
  13. ©Gunosy Inc. 13 ࣍ʹhandler

  14. ©Gunosy Inc. 14 handler (1) // article/api_article.go package article import

    ( "context" "net/http" "github.com/azihsoyn/IDDD_go_sample/internal/repository" ) func ArticleHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { req := articleRequest{} if err := req.Parse(ctx); err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } a, err := repository.FromContext(ctx).ArticleRepository.ResolveByID(req.ArticleID) if err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } renderArticleView(w, a) }
  15. ©Gunosy Inc. 15 handler (2) - request // article/request_article.go package

    article import ( "context" "strconv" "github.com/azihsoyn/IDDD_go_sample/internal/domain/article" "github.com/guregu/kami" ) type articleRequest struct { ArticleID article.Identifier } func (req *articleRequest) Parse(ctx context.Context) error { articleID, err := strconv.ParseInt(kami.Param(ctx, "article_id"), 10, 64) if err != nil { return err } req.ArticleID = article.Identifier(articleID) return nil }
  16. ©Gunosy Inc. 16 handler (3) - view // article/view_article.go package

    article import ( "net/http" "github.com/azihsoyn/IDDD_go_sample/internal/domain/article" ) type articleView struct { ID int64 `json:"id"` Title string `json:"title"` Content string `json:"content"` } func renderArticleView(w http.ResponseWriter, a article.Article) { view := articleView{ ID: int64(a.ID), Title: a.Title, Content: a.Content, } renderer.JSON(w, http.StatusOK, view) }
  17. ©Gunosy Inc. 17 ▪ handlerͰ΍Δ͜ͱΛγϯϓϧʹ͢Δ – ϦΫΤετͷύʔε&όϦσʔγϣϯ – modelΛऔಘ –

    Ϩεϙϯε༻ʹviewΛੜ੒ ΤϥʔϋϯυϦϯά͕ଟ͘ͳΒͳ͍Α͏ʹͯ͠Δ ▪ ϑΝΠϧΛׂΓͱ෼ׂ͢Δ – ϦΫΤετ༻ͷϑΝΠϧ – Ϩεϙϯε༻ͷϑΝΠϧ – handler༻ͷϑΝΠϧ ϑΝΠϧ໊ͰԿͯ͠Δ͔෼͔ΔΑ͏ʹ handlerͰҙ͍ࣝͯ͠Δ͜ͱ ͱʹ͔͘ݟ΍͘͢γϯϓϧʹʂ
  18. ©Gunosy Inc. 18 ଓ͍ͯmodelͷఆٛ

  19. ©Gunosy Inc. 19 modelͷఆٛ // internal/domain/article/article.go package article type Identifier

    int64 type Article struct { ID Identifier Title string Content string }
  20. ©Gunosy Inc. 20 modelͷऔಘ(repository) // internal/domain/article/repository.go package article //go:generate mockgen

    -package article -source repository.go -destination mock/ repository_mock.go -imports .=github.com/azihsoyn/IDDD_go_sample/internal/domain/article type Repository interface { ResolveByID(articleID Identifier) (Article, error) ResolveAll() ([]Article, error) }
  21. ©Gunosy Inc. 21 ▪ modelΛhandlerͷviewͱڞ༻ʹ͠ͳ͍ – modelʹ͸jsonͷstruct tag͸͚ͭͳ͍ ▪ model͸DBͷςʔϒϧߏ଄ͱҰக͢Δͱ͸ݶΒͳ͍

    – joinͨ݁͠Ռͷ৔߹΋͋Δ – ෆཁͳΧϥϜΛmodelʹ࣋ͭඞཁ͸ͳ͍ ▪ ϑΝΠϧΛׂΓͱ෼ׂ͢Δ – model༻ͷϑΝΠϧ – interface༻ͷϑΝΠϧ ϑΝΠϧ໊ͰԿͯ͠Δ͔෼͔ΔΑ͏ʹ modelͰҙ͍ࣝͯ͠Δ͜ͱ1
  22. ©Gunosy Inc. 22 ▪ repositoryΛinterfaceʹ࣮ͯ͠૷ͱ෼཭͢Δ – articleͱ͍͏modelΛͲ͔͜Βऔಘ͢Δ͔Λҙࣝ͠ͳ͍ – interfaceʹ͓ͯ͘͜͠ͱͰmockͷੜ੒͕؆୯ ▪

    gomockΛ࢖ͬͯinterface͔ΒmockΛੜ੒ – https://github.com/golang/mock – mock͸mock༻ͷσΟϨΫτϦʹ
 articleσΟϨΫτϦͷΧόϨοδΛԼ͛ͳ͍ͨΊ
 (CI࣌ʹmockΛੜ੒ͯ͠ίϛοτ͠ͳ͍ํ๏΋͋Δͱ͔) ▪ ࢀরܥ͸ResolveXXXɺߋ৽ܥ͸StoreXXX – ໋໊Ͱ೰·ͳ͍Α͏ʹ – ྫ֎΋͋Δ modelͰҙ͍ࣝͯ͠Δ͜ͱ2 ͱʹ͔͘ݟ΍͘͢γϯϓϧʹʂ
  23. ©Gunosy Inc. 23 repositoryͷ࣮૷(mock൛)

  24. ©Gunosy Inc. 24 // main.go package main import ( "context"

    "github.com/azihsoyn/IDDD_go_sample/article" "github.com/azihsoyn/IDDD_go_sample/internal/repository" "github.com/guregu/kami" ) func main() { ctx := context.Background() ctx = repository.NewContext(ctx) kami.Context = ctx kami.Get("/articles", article.ArticleListHandler) kami.Get("/article/:article_id", article.ArticleHandler) kami.Serve() }
  25. ©Gunosy Inc. 25 ·ͣ͸context͔ΒrepositoryΛऔΕΔΑ͏ʹ // internal/repository/context.go package repository import (…)

    type Repository struct { ArticleRepository article.Repository // ଞͷmodelͷrepository͕Ͱ͖ͨΒ௥Ճ͍ͯ͘͠ } func NewContext(ctx context.Context) context.Context { return context.WithValue(ctx, contextKey, newRepository(ctx)) }
  26. ©Gunosy Inc. 26 mockΛੜ੒ $ mkdir ./internal/domain/article/mock $ go generate

    ./...
  27. ©Gunosy Inc. 27 mockΛ࢖ͬͯrepositoryΛ࣮૷ // internal/repository/context.go func newRepository(ctx context.Context) *Repository

    { t := new(testing.T) ctrl := gomock.NewController(t) repo := articlemock.NewMockRepository(ctrl) repo.EXPECT().ResolveByID(article.Identifier(1)).Return(article.Article{ ID: 1, Title: "hello", Content: "world", }, nil).Times(100) return &Repository{ ArticleRepository: repo, } } ͜͜·ͰͰAPIαʔόͱͯ͠ಈ͘Α͏ʹͳΔ
  28. ©Gunosy Inc. 28 repositoryͷ࣮૷(ͪΌΜͱͨ͠൛)

  29. ©Gunosy Inc. 29 // internal/repository/article/repository.go package articlerepo import (…) type

    articleRepository struct{} var _ article.Repository = (*articleRepository)(nil) func New() article.Repository { return &articleRepository{} } func (repo *articleRepository) ResolveByID(articleID article.Identifier) (article.Article error) { // DB΍Ωϟογϡ͔ΒarticleΛऔಘ͢Δॲཧ return article.Article{}, errors.New("not implemented") } func (repo *articleRepository) ResolveAll() ([]article.Article, error) { // DB΍Ωϟογϡ͔ΒarticleΛऔಘ͢Δॲཧ return nil, errors.New("not implemented") }
  30. ©Gunosy Inc. 30 // internal/repository/context.go package repository func newRepository(ctx context.Context)

    *Repository { /* t := new(testing.T) ctrl := gomock.NewController(t) repo := articlemock.NewMockRepository(ctrl) repo.EXPECT().ResolveByID(article.Identifier(1)).Return(article.Article{ ID: 1, Title: "hello", Content: "world", }, nil).Times(100) */ repo := articlerepo.New() return &Repository{ ArticleRepository: repo, } } context.goΛ௚͚ͩ͢Ͱmockͱ࣮૷͕੾ΓସΘΔ
  31. ©Gunosy Inc. 31 ͜͜·Ͱ͸γϯϓϧͳ࣮૷ͩͬͨͷͰ ΋͏গ͠ෳࡶͳ࣮૷Λߟ͑ͯΈΔ

  32. ©Gunosy Inc. 32 ϢʔβʔʹΑͬͯهࣄΛग़͠෼͚͍ͨ ͱ͍͏ཁ๬͕͋ͬͨͱ͖

  33. ©Gunosy Inc. 33 ͜Μͳॲཧ func UserSpecificArticles(as []Article, userID int64) []Article

    { ret := make([]Article, 0, len(as)) // userID͕ಛఆͷ஋(= 1)ͩͬͨΒهࣄID͕100ͷهࣄΛઌ಄ʹग़͢ if userID == 1 { ret = append(ret, Article{ ID: 100, Title: "for user 1", Content: "this is recommended by your activity", }) } ret = append(ret, as...) return ret } Ͳ͜ʹ࣮૷͢Δ͔ʁ
  34. ©Gunosy Inc. 34 Ҋ1) articleͷrepositoryʹ࣮૷ repository͸͋͘·ͰσʔλετΞͱͷ΍ΓͱΓʹཹΊΔ΂͖ // internal/repository/article/repository.go package articlerepo

    func (repo *articleRepository) ResolveAll() ([]article.Article, error) { as, err := repo.resolveAllFromMySQL() if err != nil { return nil, err } as = userSpecificArticles(as, req.UserID) return ret } ✕
  35. ©Gunosy Inc. 35 Ҋ2) handlerʹ࣮૷ handler͕articleΛՃ޻ͯ͠͠·͍੹຿͕෼ࢄͯ͠Δ // article/api_article_list.go package article

    import (…) func ArticleListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { req := articleListRequest{} if err := req.Parse(ctx, r); err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } as, err := repository.FromContext(ctx).ArticleRepository.ResolveAll(req.UserID) if err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } as = userSpecificArticles(as, req.UserID) renderArticleListView(w, as) } ˚
  36. ©Gunosy Inc. 36 Ҋ3) domain modelʹ࣮૷͢Δ هࣄͷϩδοΫͳͷͰ੹຿͸Αͦ͞͏ɻ ͨͩhandlerͰݺͼग़͢ͷ͸มΘΒͳ͍ ̋ //

    article/api_article_list.go package article import (…) func ArticleListHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { req := articleListRequest{} if err := req.Parse(ctx, r); err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } as, err := repository.FromContext(ctx).ArticleRepository.ResolveAll(req.UserID) if err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } as = article.UserSpecificArticles(as, req.UserID) renderArticleListView(w, as) }
  37. ©Gunosy Inc. 37 ▪ handlerͰ΍Δ͜ͱΛγϯϓϧʹ͢Δ – ϦΫΤετͷύʔε&όϦσʔγϣϯ – modelΛऔಘ –

    Ϩεϙϯε༻ʹviewΛੜ੒ ΤϥʔϋϯυϦϯά͕ଟ͘ͳΒͳ͍Α͏ʹͯ͠Δ ▪ ϑΝΠϧΛׂΓͱ෼ׂ͢Δ – ϦΫΤετ༻ͷϑΝΠϧ – Ϩεϙϯε༻ͷϑΝΠϧ – handler༻ͷϑΝΠϧ ϑΝΠϧ໊ͰԿͯ͠Δ͔෼͔ΔΑ͏ʹ handlerͰҙ͍ࣝͯ͠Δ͜ͱ(࠶ܝ) ͱʹ͔͘ݟ΍͘͢γϯϓϧʹʂ
  38. ©Gunosy Inc. 38 handlerͰϏδωεϩδοΫΛ࢖͏͜ͱʹͳΔͷͰ ॲཧ͕ෳࡶʹͳ͍ͬͯ͘ͱhandler͕ͲΜͲΜංେԽ͍ͯ͘͠

  39. ©Gunosy Inc. 39 Ͳ͏͢Δ͔

  40. ©Gunosy Inc. 40 αʔϏεͱ͍͏΋ͷΛ༻ҙ͢Δ

  41. ©Gunosy Inc. 41 “ ཁٻʹԠͯ͡ΫϥΠΞϯτͷͨΊʹߦΘΕΔԿ͔Ͱ͋Δɻ” “ Ϟσϧʹ͓͍ͯಠཱͨ͠ΠϯλϑΣʔεͱͯ͠ఏڙ͞ΕΔૢ࡞” ΤϦοΫɾΤϰΝϯεͷυϝΠϯۦಈઃܭ ୈ2෦ ୈ5ষ

    ΑΓ αʔϏεͱ͸
  42. ©Gunosy Inc. 42 ???

  43. ©Gunosy Inc. 43 - ෳ਺ͷϞσϧΛѻͬͯෳࡶͳ͜ͱΛͯ͘͠ΕΔ΍ͭ - repository͔Βऔಘͨ͠objectΛ͞ΒʹՃ޻͢Δ΍ͭ ͜Μͳೝࣝ

  44. ©Gunosy Inc. 44 αʔϏεͰ࣮૷ (1) // internal/domain/article/user_specific_article_service.go package article type

    UserSpecificArticleService interface { Get(userID int64) ([]Article, error) } // internal/service/article/user_specific_article_service.go type userSpecificArticleService struct { repo Repository } func NewUserSpecificArticleService(articleRepo Repository) UserSpecificArticleService { return &userSpecificArticleService{ repo: articleRepo, } }
  45. ©Gunosy Inc. 45 αʔϏεͰ࣮૷ (2) func (s *userSpecificArticleService) Get(userID int64)

    ([]Article, error) { as, err := s.repo.ResolveAll() if err != nil { return nil, err } as = article.UserSpecificArticles(as, userID) return as, nil } service͕repository΍ϏδωεϩδοΫΛ࢖͏
  46. ©Gunosy Inc. // article/api_article_list.go package article import (…) func ArticleListHandler(ctx

    context.Context, w http.ResponseWriter, r *http.Request) { req := articleRequest{} if err := req.Parse(ctx); err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } as, err := service.FromContext(ctx).UserSpecificArticleService.Get(req.UserID) if err != nil { renderer.JSON(w, http.StatusBadRequest, err.Error()) } renderArticleListView(w, as) } 46 αʔϏεͰ࣮૷ (3) handlerͷγϯϓϧ͕͞อͨΕͨ ˕
  47. ©Gunosy Inc. 47 ಛఆͷϢʔβʔʹରͯ͠ಛఆͷهࣄΛ௥Ճͨ͠Γ͠ͳ͔ͬͨΓ͢Δ ̍ ಛఆͷϢʔβʔʹରͯ͠ಛఆͷλϒΛ௥Ճͨ͠Γฒͼସ͑ͨΓ͢Δ ̎ GunosyͰαʔϏεʹͯ͠Δॲཧྫ ▪ A/Bςετͱ͔

    ▪ ߟ͑ΒΕΔύϥϝʔλ – ϢʔβʔID OS ΞϓϦέʔγϣϯͷόʔδϣϯ...etc ▪ A/Bςετͱ͔ ▪ ߟ͑ΒΕΔύϥϝʔλ – ϢʔβʔID OS ΞϓϦέʔγϣϯͷόʔδϣϯ...etc xxx_serviceͬͯͷ͕͋ͬͨΒԿ͔ෳࡶͳ͜ͱΛ΍ͬͯΔͳͬͯΠϝʔδ
  48. ©Gunosy Inc. 48 ϋϚͬͨͱ͜Ζ

  49. ©Gunosy Inc. 49 ϋϚͬͨͱ͜Ζ ▪ package໊ͷॏෳ – domainͱrepository͕େମಉ͡ߏ੒ʹͳΔͨΊ – ࢓ํ͕ͳ͍ͷͰrepositoryͷํʹsuffixΛ͚͍ͭͯΔ

    – ྫ) repository/article → package artilcerepo – ߋʹimport͢Δͱ͖͸໊લ෇͖ʹ͠ͳ͍ͱ͏·͘ิ׬͕ޮ͔ͳ͍ ▪ cyclic import – σΟϨΫτϦΛ෼ׂ͢Δฐ֐ – ਂ͍֊૚ʹ͍ΔϑΝΠϧͷςετͰϓϩδΣΫτશମͰ࢖͑ΔΑ͏ ͳศརhelperΛݺͼग़ͦ͏ͱͯ͠Τϥʔ͍ͬͯ͏ͷ͕Α͋ͬͨ͘ – σΟϨΫτϦ΍ϑΝΠϧΛখ͍ͯ͘͞͠ΔͷͰศརhelperʹཔΒͣʹ ςετΛॻ͘Α͏ʹͨ͠
  50. ©Gunosy Inc. 50 ·ͱΊ ▪ DDD͸ҰਓͰ΍ͬͯ΋ҙຯ͕͋Δʂ(ͱࢥ͏) – Ͳ͜ΛͲ͏௚ͤ͹͍͍͔͙͢ΠϝʔδͰ͖Δ – मਖ਼ൣғΛখ͘͞Ͱ͖Δ

    – ϨϏϡʔ͠΍͘͢Ͱ͖Δ(ࠓޙਓ͕૿͑ͨͱ͖) ▪ ϑΝΠϧ͸ҙຯͷ͋Δ୯Ґʹ෼ׂ͢Δ ▪ ύοέʔδ΋ҙຯͷ͋Δ୯Ґʹ෼ׂ͢Δ ▪ ؾ͍ͮͨΒϦϑΝΫλϦϯά͢Δ – ύοέʔδʹҧ࿨ײ͕Ͱ͖ͯͨΒ – ϦϑΝΫλ͠΍͍͢Α͏ʹϦϑΝΫλ͍ͯ͘͠
  51. ©Gunosy Inc. 51 Gunosy͸Ұॹʹ՝୊Λղܾ͢Δ Go / Python ΤϯδχΞΛืू͍ͯ͠·͢ʂ https://js01.jposting.net/gunosy/u/job.phtml?job_code=6

  52. ©Gunosy Inc. 52 ͦͷଞࢿྉ ▪ https://github.com/azihsoyn/IDDD_go_sample – ࠓճͷαϯϓϧϓϩδΣΫτ ▪ https://github.com/guregu/kami

    – contextରԠͷWAF ▪ https://github.com/azihsoyn/lwcache – [એ఻] ϓϩηεΩϟογϡϥΠϒϥϦ ▪ https://github.com/golang/mock – interface͔Βmockੜ੒ͯ͘͠ΕΔπʔϧ