Slide 1

Slide 1 text

࣮ફGoGoGo Gunosy Inc. 2016.10

Slide 2

Slide 2 text

©Gunosy Inc. 2 ࣗݾ঺հ ■ azihsoyn (͋͡;ͦ΍Μ) – Twitter – Github – …etc ■ ۀ຿ – GoͰAPIαʔόͷ։ൃɾӡ༻ ■ झຯ – ௼Γ(6ׂ) ● ઒΍ւͰϧΞʔ௼Γ – Ξχϝ(4ׂ) ● μΠϠ༷ʙ ■ ۙگ – isucon6ຊઓग़·͢ʂ
 (νʔϜ: ͝஫จ͸poyoͰ͔͢ʁ) ٢ᖒ ௚࠸

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

©Gunosy Inc. 4 GoͰDDD΍ͬͯΔ࿩ ࠓ೔ͷ࿩

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

©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) ͜ͷ͋ͨΓͷίʔυಡΈͳ͕ΒϓϩμΫτʹམͱ͜͠Μͩײ͡Ͱ͢

Slide 7

Slide 7 text

©Gunosy Inc. 7 ࣝऀͷํʑ͔ΒͷಥͬࠐΈେ׻ܴͰ͢

Slide 8

Slide 8 text

©Gunosy Inc. 8 վΊͯࠓ೔ͷ࿩ ࿩͢͜ͱ ࿩͞ͳ͍͜ͱ ■ GoͰAPIॻ͘ͱ͖ʹؾΛ͚ͭͯΔ͜ͱ ■ ύοέʔδߏ੒ ■ ϋϚͬͨ͜ͱ ■ DDDͷ༻ޠͷઆ໌ͱ͔ ■ ϥΠϒϥϦͷ࢖͍ํͱ͔ GoͰDDDΛ΍ͬͯࢼߦࡨޡͨ͠࿩Λ͍ͨ͠ͱࢥ͍·͢

Slide 9

Slide 9 text

©Gunosy Inc. 9 هࣄΛฦ͢APIΛ࡞Δ৔߹Λ૝ఆͯ͠ΈΔ

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

©Gunosy Inc. 11 ·ͣ͸main.go

Slide 12

Slide 12 text

©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ͯ͠ΔҎ֎ಛʹมΘͬͨ͜ͱ͸ͯ͠ͳ͍

Slide 13

Slide 13 text

©Gunosy Inc. 13 ࣍ʹhandler

Slide 14

Slide 14 text

©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) }

Slide 15

Slide 15 text

©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 }

Slide 16

Slide 16 text

©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) }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

©Gunosy Inc. 18 ଓ͍ͯmodelͷఆٛ

Slide 19

Slide 19 text

©Gunosy Inc. 19 modelͷఆٛ // internal/domain/article/article.go package article type Identifier int64 type Article struct { ID Identifier Title string Content string }

Slide 20

Slide 20 text

©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) }

Slide 21

Slide 21 text

©Gunosy Inc. 21 ■ modelΛhandlerͷviewͱڞ༻ʹ͠ͳ͍ – modelʹ͸jsonͷstruct tag͸͚ͭͳ͍ ■ model͸DBͷςʔϒϧߏ଄ͱҰக͢Δͱ͸ݶΒͳ͍ – joinͨ݁͠Ռͷ৔߹΋͋Δ – ෆཁͳΧϥϜΛmodelʹ࣋ͭඞཁ͸ͳ͍ ■ ϑΝΠϧΛׂΓͱ෼ׂ͢Δ – model༻ͷϑΝΠϧ – interface༻ͷϑΝΠϧ ϑΝΠϧ໊ͰԿͯ͠Δ͔෼͔ΔΑ͏ʹ modelͰҙ͍ࣝͯ͠Δ͜ͱ1

Slide 22

Slide 22 text

©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 ͱʹ͔͘ݟ΍͘͢γϯϓϧʹʂ

Slide 23

Slide 23 text

©Gunosy Inc. 23 repositoryͷ࣮૷(mock൛)

Slide 24

Slide 24 text

©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() }

Slide 25

Slide 25 text

©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)) }

Slide 26

Slide 26 text

©Gunosy Inc. 26 mockΛੜ੒ $ mkdir ./internal/domain/article/mock $ go generate ./...

Slide 27

Slide 27 text

©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αʔόͱͯ͠ಈ͘Α͏ʹͳΔ

Slide 28

Slide 28 text

©Gunosy Inc. 28 repositoryͷ࣮૷(ͪΌΜͱͨ͠൛)

Slide 29

Slide 29 text

©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") }

Slide 30

Slide 30 text

©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ͱ࣮૷͕੾ΓସΘΔ

Slide 31

Slide 31 text

©Gunosy Inc. 31 ͜͜·Ͱ͸γϯϓϧͳ࣮૷ͩͬͨͷͰ ΋͏গ͠ෳࡶͳ࣮૷Λߟ͑ͯΈΔ

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

©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 } Ͳ͜ʹ࣮૷͢Δ͔ʁ

Slide 34

Slide 34 text

©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 } ✕

Slide 35

Slide 35 text

©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) } ˚

Slide 36

Slide 36 text

©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) }

Slide 37

Slide 37 text

©Gunosy Inc. 37 ■ handlerͰ΍Δ͜ͱΛγϯϓϧʹ͢Δ – ϦΫΤετͷύʔε&όϦσʔγϣϯ – modelΛऔಘ – Ϩεϙϯε༻ʹviewΛੜ੒ ΤϥʔϋϯυϦϯά͕ଟ͘ͳΒͳ͍Α͏ʹͯ͠Δ ■ ϑΝΠϧΛׂΓͱ෼ׂ͢Δ – ϦΫΤετ༻ͷϑΝΠϧ – Ϩεϙϯε༻ͷϑΝΠϧ – handler༻ͷϑΝΠϧ ϑΝΠϧ໊ͰԿͯ͠Δ͔෼͔ΔΑ͏ʹ handlerͰҙ͍ࣝͯ͠Δ͜ͱ(࠶ܝ) ͱʹ͔͘ݟ΍͘͢γϯϓϧʹʂ

Slide 38

Slide 38 text

©Gunosy Inc. 38 handlerͰϏδωεϩδοΫΛ࢖͏͜ͱʹͳΔͷͰ ॲཧ͕ෳࡶʹͳ͍ͬͯ͘ͱhandler͕ͲΜͲΜංେԽ͍ͯ͘͠

Slide 39

Slide 39 text

©Gunosy Inc. 39 Ͳ͏͢Δ͔

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

©Gunosy Inc. 42 ???

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

©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, } }

Slide 45

Slide 45 text

©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΍ϏδωεϩδοΫΛ࢖͏

Slide 46

Slide 46 text

©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ͷγϯϓϧ͕͞อͨΕͨ ˕

Slide 47

Slide 47 text

©Gunosy Inc. 47 ಛఆͷϢʔβʔʹରͯ͠ಛఆͷهࣄΛ௥Ճͨ͠Γ͠ͳ͔ͬͨΓ͢Δ ̍ ಛఆͷϢʔβʔʹରͯ͠ಛఆͷλϒΛ௥Ճͨ͠Γฒͼସ͑ͨΓ͢Δ ̎ GunosyͰαʔϏεʹͯ͠Δॲཧྫ ■ A/Bςετͱ͔ ■ ߟ͑ΒΕΔύϥϝʔλ – ϢʔβʔID OS ΞϓϦέʔγϣϯͷόʔδϣϯ...etc ■ A/Bςετͱ͔ ■ ߟ͑ΒΕΔύϥϝʔλ – ϢʔβʔID OS ΞϓϦέʔγϣϯͷόʔδϣϯ...etc xxx_serviceͬͯͷ͕͋ͬͨΒԿ͔ෳࡶͳ͜ͱΛ΍ͬͯΔͳͬͯΠϝʔδ

Slide 48

Slide 48 text

©Gunosy Inc. 48 ϋϚͬͨͱ͜Ζ

Slide 49

Slide 49 text

©Gunosy Inc. 49 ϋϚͬͨͱ͜Ζ ■ package໊ͷॏෳ – domainͱrepository͕େମಉ͡ߏ੒ʹͳΔͨΊ – ࢓ํ͕ͳ͍ͷͰrepositoryͷํʹsuffixΛ͚͍ͭͯΔ – ྫ) repository/article → package artilcerepo – ߋʹimport͢Δͱ͖͸໊લ෇͖ʹ͠ͳ͍ͱ͏·͘ิ׬͕ޮ͔ͳ͍ ■ cyclic import – σΟϨΫτϦΛ෼ׂ͢Δฐ֐ – ਂ͍֊૚ʹ͍ΔϑΝΠϧͷςετͰϓϩδΣΫτશମͰ࢖͑ΔΑ͏ ͳศརhelperΛݺͼग़ͦ͏ͱͯ͠Τϥʔ͍ͬͯ͏ͷ͕Α͋ͬͨ͘ – σΟϨΫτϦ΍ϑΝΠϧΛখ͍ͯ͘͞͠ΔͷͰศརhelperʹཔΒͣʹ ςετΛॻ͘Α͏ʹͨ͠

Slide 50

Slide 50 text

©Gunosy Inc. 50 ·ͱΊ ■ DDD͸ҰਓͰ΍ͬͯ΋ҙຯ͕͋Δʂ(ͱࢥ͏) – Ͳ͜ΛͲ͏௚ͤ͹͍͍͔͙͢ΠϝʔδͰ͖Δ – मਖ਼ൣғΛখ͘͞Ͱ͖Δ – ϨϏϡʔ͠΍͘͢Ͱ͖Δ(ࠓޙਓ͕૿͑ͨͱ͖) ■ ϑΝΠϧ͸ҙຯͷ͋Δ୯Ґʹ෼ׂ͢Δ ■ ύοέʔδ΋ҙຯͷ͋Δ୯Ґʹ෼ׂ͢Δ ■ ؾ͍ͮͨΒϦϑΝΫλϦϯά͢Δ – ύοέʔδʹҧ࿨ײ͕Ͱ͖ͯͨΒ – ϦϑΝΫλ͠΍͍͢Α͏ʹϦϑΝΫλ͍ͯ͘͠

Slide 51

Slide 51 text

©Gunosy Inc. 51 Gunosy͸Ұॹʹ՝୊Λղܾ͢Δ Go / Python ΤϯδχΞΛืू͍ͯ͠·͢ʂ https://js01.jposting.net/gunosy/u/job.phtml?job_code=6

Slide 52

Slide 52 text

©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ੜ੒ͯ͘͠ΕΔπʔϧ