Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Goでやや大きいプロダクトのアーキテクチャを検討したり実装したりしたおはなし

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for yami20 yami20
July 17, 2020

 Goでやや大きいプロダクトのアーキテクチャを検討したり実装したりしたおはなし

Avatar for yami20

yami20

July 17, 2020
Tweet

Other Decks in Technology

Transcript

  1. ཁ݅ɾن໛ɾಛੑ ʙอݥϓϥοτϑΥʔϜγεςϜʙ •(͜ͷล͸ࢀߟఔ౓ʹॻ͍͚ͨͩͳͷͰඈ͹͠·͢) • ཁ݅ • APIαʔόʔ(SPAͷbackend) • ͪͳΈʹରʹͳΔfrontendͷ͸ͳ͠͸ https://speakerdeck.com/slont/bokufalsekangaetasaikiyoufalsevueakitekutiya

    • ෳ਺ͷอݥձ͕ࣾ঎඼Λઃܭɾొ࿥͠ɺސ٬ͷਃࠐΛड͚෇͚ͨΓɺܖ໿৹ࠪͨ͠Γ؅ཧͨ͠Γɺͦͷଞ෇ਵ͢Δػೳ͕΋ʹΐ΋ʹΐɻ • ن໛ • ΞυςΫ΍ήʔϜʹൺ΂ΔͱτϥϑΟοΫ͸֨ஈʹ͓ͱͳ͍͠ɻ1ਓ͕1೔ʹԿेճ΋Ճೖͨ͠Γ͠ͳ͍͠ɺ࿈ଧͯ͠౗͢ఢ΋͍ͳ͍ɻ • PFͰ͋Δ͜ͱΛ౿·͑ͯ΋ҰൠతͳECͱ͔ͷن໛ײΛΠϝʔδ͍͚ͯͨͩ͠Ε͹ɻ • ಛੑ • σʔλͷϥΠϑαΠΫϧ͕௕͍ɻ೥୯Ґ͕جຊɺਓੜ୯ҐͷऔҾ΋͋Δ • σʔλͷߏ଄͕ෳࡶ. ଐੑ΋ϦϨʔγϣϯ΋ͱʹ͔͘ଟ͍. • γεςϜͷण໋͕௕͍ɻσʔλͷϥΠϑαΠΫϧ͕௕͍ͷ΋͋Δ͠ɺPFͳͷͰ͓͍ͦΕͱด͡ΒΕͳ͍ͱ͍͏ͷ΋͋Δɻ • PFͰ͋ΔͨΊɺॳظʹग़Δཁ͕݅͢΂ͯͱ͸ݶΒͳ͍ • ࢥ͍΋ΑΒͳ͍֦ு͕ඞཁʹͳΔՄೳੑ͕͋Δ
  2. ϞϊϦεͱϚΠΫϩαʔϏε • ϞϊϦεʹ਌Λࡴ͞Εͨܦݧ͸͋Δɻ • ϑϩϯτ͔Β؅ཧը໘·Ͱ͢΂͕ͯಉࠝ͞Εɺ100ਓҎ্ͷ։ൃऀ͕ҰͭͷϦϙδτϦʹ commit͠ʮdeployฒͼ·͢ʯͱ੠Λ͔͚߹͍ɺσϓϩΠࣦഊ͢Δͱଞ෦ॺʹౖΒΕΔσΟ ετϐΞ • ϚΠΫϩαʔϏεʹଜΛম͔Εͨܦݧ΋͋Δɻ •

    1~3ςʔϒϧ૬౰ͷσʔλຖʹઐ༻ͷAPI͕ཚཱͯ͠૬ޓʹԿ౓΋ࢀর͍͋͠ɺ͍ͭͲ ͜ͰԿͷॲཧ͕ى͖͍ͯΔ͔Θ͔Βͳ͍σΟετϐΞ • ݁ہɺ͓࡞๏ʹࠩͷ͋Δଟ਺ͷγεςϜΛ͢΂ͯ೺Ѳ͠ͳ͚Ε͹Կ΋Θ͔Βͳ͍ɻ
  3. ϞϊϦεͱϚΠΫϩαʔϏε • ϞϊϦε • ϝϯςෆՄೳͳ΄Ͳίʔυɾؔ܎ऀ͕ଟ͘ͳΔͱͭΒ͍ • ಉҰϦϙδτϦ಺͔ͩΒͱແࠩผʹࢀরΛΏΔ͢ͱΘ͚Θ͔ΒΜ͘ͳΔ • ϚΠΫϩαʔϏε •

    ෆద੾ͳ෼ׂʹΑͬͯຊདྷඞཁͷͳ͍ॲཧ͕૿Ճ͢Δͱͱͯ΋ͭΒ͍ • Ұൠతʹ࣮૷ྔ͸૿͑Δ͠ɺΠϯϑϥߏ੒໘Ͱͷݕ౼ࣄ߲ɾઃఆ΋૿͑Δ
  4. ϞϊϦεͱ͍͏બ୒ • ࠷దͳϚΠΫϩαʔϏεͷཻ౓Λࣄલʹݟग़͢͜ͱ͸ඇৗʹ೉қ౓͕ߴ͍ɻ • ࢓༷௨Γʹ࣮૷͍ͯͨ͠ͷʹʮ࣮͸Aͷσʔλ͚ͩͩͱ଍Γͳͯ͘ɺৗʹBͱC΋ҰॹʹཁΔΜͩ...ʯͱ͔ݴΘΕ͕ͪ • Goͬͯهड़ྔࣗମ͸গͳ͘ͳ͍Ͱ͢ΑͶ? (ڞײΛٻΊΔѹྗ) • γϯϓϧͰಡΈॻ͖͠΍͍͢෼ɺʮָΛͤͯ͘͞ΕΔʯ࢓૊Έ͸߇͑Ίɻ

    • ϚΠΫϩαʔϏεͰͷهड़૿Ճͱ߹ΘͤΔͱͪΐͬͱϔϏʔɻ • αʔϏεͷ্ཱͪ͛࣌఺ͰϞϊϦε๊͕͑Δ໰୊͕ੜ͡ΔϦεΫ͸খ͍͞ • ʮϞϊϦεͷ··ʯҭͯͳͯ͘͢ΉΑ͏ʹద੾ͳઃܭɾ੍໿Λ͠ɺ͕࣌དྷͨΒ෼ׂ͢Ε͹Α͔Ζ͏ͳ ͷͩ • αʔϏεΛҭ͍ͯͯ͘தͰద੾ͳ෼ׂཻ౓͕ݟ͑ͯ͘Δͱ͍͏ϝϦοτ΋͋Δɻ
  5. ґଘੑͷٯస(DIP) ```go:application/policy.go package application type Policy struct { // DIP.

    infraʹґଘͤͣ͞ʹdomainͷinterfaceʹґଘͤ͞Δ policyRepo domain.PolicyRepository } func (p *Policy) Get(id string) (*model.Policy, error) { // ... res, err := p.policyRepo.Get(id) if err != nil { return nil, err } return res, nil } ``` ```go:domain/repository.go package domain // application͔Βݟ͑Δ৔ॴʹinterfaceΛஔ͘ type PolicyRepository interface { Get(id string) (*model.Policy, error) } ```
  6. ϨΠϠʔυΞʔΩςΫνϟ෩ʹຬͨ͢ͱ... ```go:infra/db/policy.go package infra // infra/db ʹ interface Λຬͨ͢۩ମతͳ࣮૷Λஔ͘ type

    PolicyRepository struct { sess *dbr.Session } func (p *PolicyRepository) Get(id string) (*model.Policy, error) { // SQLͱ͔ // p.sess. ... } ```
  7. ΫϦʔϯΞʔΩςΫνϟ෩ʹຬͨ͢ͱ... ```go:infra/db/policy.go // infra type DBSess struct { sess *dbr.Session

    } func NewDBSess() *DBSess { // DB઀ଓ } func (d *DBSess) Query(stmt string, args ...interface{}) () {} ``` ```go:interfaces/db.go // interfaces type DBSess interface { Query(stmt string, args ...interface{}) (Rows, error) } type Rows interface { Scan(...interface{}) error Next() bool Close() error } type PolicyRepository struct { sess DBSess } func (p *PolicyRepository) Get(id string) (*model.Policy, error) { // SQLͱ͔ // p.handler.... } ```
  8. ઃܭͷझࢫ • ࣮ફDDDʹ͍͏ʮ؇΍͔ͳϨΠϠʔԽΞʔΩςΫνϟʯ͕ϕʔε • ʮ؇΍͔ʯͱ͸ɺ௚Լͷ૚͚ͩͰͳ͘Լํ޲ͷ૚͸͍ͣΕ΋࢖ͬͯΑ͍ͱ͍͏ҙຯɻ • ໋໊ɾσΟϨΫτϦͷ੾Γํ͸ΘΓͱ;Μ͍͖ɻ • ڭՊॻతʹ͸ interfaces/handler

    ͱ͔͕ͩɺinterfaces͕όϦΤʔγϣϯʹ෋Ήҹ৅͕͋·Γ ͳ͘ɺtopϨϕϧͷσΟϨΫτϦ͕1ͭ2ͭ૿͑ͨͱͯࠔΔ͜ͱ΋ͳ͍ͷͰtopʹhandlerͰ ͖ͬͨɻ·͊ͲͬͪͰ΋͍͍͔ͳɻ • application͡Όͳͯ͘usecase͚ͬͯͭΔ͜ͱ͕ଟ͍ؾ΋͢Δɻ
 ݸਓతʹusecaseͬͯଧͭͱࠨख͕ർΕΔ͔ΒڭՊॻΛ໔ࡑූʹapplicationʹͨ͠ɻ
  9. ੍໿ʹΑͬͯಘΔ΋ͷᶄ • application͸repositoryͷinterfaceͷΈʹґଘ͠infraʹ͸ґଘ͍ͯ͠ͳ͍ɻ • infraΛࠩ͠ସ͑Δࣄ͕Ͱ͖Δɻ • mockΛࠩ͠ࠐΜͰͷunit test͕Մೳ • ͜ͷ৔߹ɺྫ͑͹ʮσʔλ͕ਖ਼͘͠อଘ͞ΕΔ͔ʯͷςετͰͳ͘ɺʮσʔλΛਖ਼͘͠૊ΈཱͯͯอଘΛݺ΂Δ͔ʯͷςετʹͳΔ

    • ࣮ࡍʹʮDBʹॻ͖ࠐ·ΕΔ͔·Ͱ֬ೝ͍ͨ͠ʯͱ͍͏࿩͸Θ͔Δ͕ʮapplication୯ମͷ੹຿ͷςετʯͱͯ͠͸͜ΕͰΑ͍͸ͣɻ infra࣮૷͚͕ͩbugͬͨ৔߹ʹ΋ɺapp͕bug͍ͬͯͳ͍͜ͱ͕อূ͞ΕΔɻ • localͰ࢖͑ͳ͍APIΛμϛʔʹࠩ͠ସ͑ͯಈ͔ͨ͠Γ͢Δ͜ͱ΋Մೳ
  10. Α΋΍·: handlerͷςετ • application΍handlerͷinterfaceԽ͸΍͍ͬͯͳ͍ • ΍ͬͨ΄͏͕handler,routingͷUT͸͠΍͍͢...͔ͳ? • (ബ͍࣮૷ͳΒ͹) handlerͷςετ͸UTΑΓe2eΛؤுͬͨ΄͏͕Α͍ͱࢥ͍ͬͯΔɻ •

    handler, ࣮ࡍͷreq/resͰࢼͨ͘͠ͳΔͷ͕ਓ৘Ͱ͸͋Γ·ͤΜ͔ • ʮinfraͷҟৗʹର͢ΔhandlerͷڍಈͷςετΛৗʹಈ͔͍ͨ͠ʯͱ͔ͳΒඞཁͦ͏ɻ • mockࠩ͠ࠐΜͰαʔόʔ্͛ͯe2eͰҰ౓ಈ࡞֬ೝ͢Ε͹ݸਓతʹ͸ຬ଍
  11. Α΋΍·: gomockͱDDD func TestGetProduct(t *testing.T) { ctrl := gomock.NewController(t) defer

    ctrl.Finish() // ੜ੒͞Εͨmock package m := mock_repo.NewMockProductRepository(ctrl) /* * Get("hoge")͕call͞ΕΔ͜ͱΛςετ͠ɺ * ݺ͹Εͨ৔߹ID:"hoge"ͷmodelΛฦͯ͘͠ΕΔ * / id := "hoge" m.EXPECT().Get(id).Return(&model.Product{ID: id}, nil) // mock͸RepoͷIFΛຬ͍ͨͯ͠ΔͷͰinfraͱͯ͠࢖͑Δ app := application.NewProduct(m) res, err := app.GetProduct(id) ... }
  12. Α΋΍·: testͱtime.Now() • goʹݶͬͨ࿩/େͨ͠࿩Ͱ͸ͳ͍Ͱ͕͢... • ͍Ζ͍Ζͳͱ͜Ζʹ time.Now() ͕͋ΔͨΊʹ҆ఆͨ͠ςετ͕ॻ͚ͣ ʹఘΊͨܦݧ͸͋Γ·ͤΜ͔ •

    ςετ͠΍͍͢ΞʔΩςΫνϟͰؤுͬͯॻ͍ͯ΋ɺͦ͜Ͱͭ·͍ͣͯ ͸ҙຯ͕͋Γ·ͤΜɻ • ࣌ؒ͸উखʹॻ͖׵ΘΓଓ͚Δglobalม਺ͳͷͰॳظஈ֊͔Βੵۃతʹ ௥͍ग़͓ͯ͘͠ͷ͕ྑ͍ͱࢥ͍ͬͯ·͢ɻ
  13. Α΋΍·: testͱtime.Now() package clock import "time" // Clock ࣌ؒͷinjection༻IF type

    Clock interface { Now() time.Time } // RealClock ࣮࣌ؒΛฦ͢. αʔόʔ্Ͱ͸appʹ͜ΕΛ౉͢. type RealClock struct {} func (r *RealClock) Now() time.Time { return time.Now() } func NewRealClock(loc *time.Location) *RealClock { return &RealClock{} } // MockClock ݻఆ࣌ؒΛฦ͢μϛʔ࣌ܭ.ςετͰ͸appʹ͜ΕΛ౉͢. type MockClock struct { now *time.Time } func NewMockClock(t *time.Time) *MockClock { return &MockClock{now: t} } func (m *MockClock) Now() time.Time { return time.Now() } timeΛ௥͍ग़͢Ұͭͷྫ. ͕࣌ؒඞཁͳapplicationʹ͸࣌ܭ(Now ΛऔΕΔϞϊ)Λ౉͢Α͏ʹ͓ͯ͘͠ɻ ౎౓࣌ܭ౉͢ͷΊΜͲ͍ͱ͍͏ͷ͸͋Δ͕࣌ؒ΁ͷґ ଘΛ໌ࣔతʹ͢ΔҙຯͰ͸ଥ౰ͩͱࢥ͍ͬͯΔɻ
 
 ʘ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʗ
  14. infraͷࠩ͠ସ͑,ϚϧνΫϥ΢υͱ͍͏ເ • ʮinfraҎԼʹٕज़తؔ৺Λด͡ࠐΊΔͱσʔλϕʔε΍ج൫ΛҠߦ͠Α͏ͬͯͳͬͨ࣌ʹָʯ • ࣮ࡍ໰୊ͱͯͦ͠Μͳͷ͕ඞཁʹͳΔ͜ͱͳ͍΍Ζʁ΍ͬͨ͜ͱ͋Δ΍͓ͭΓΎʙ? • ΍ͬͨɻ • ࣾ಺ͷٕज़ϙʔτϑΥϦΦଟ༷Խ΍ݱߦͷΠϯϑϥʹ໰୊͕ੜͨ͡৔߹ʹඋ͑ͯɺ৽͍ٕ͠ज़ͷݕূ΋ͯ͠Έͨ ͍ΑͶͱ͍͏࿩͕͋ͬͨɻ

    • PJͷλΠϛϯά΍ܦݧతͳ౎߹΋͋ͬͯɺGCP/CloudDatastoreΛར༻ͯ͠MVP࣮૷Λߦͬͨɻ • ͦͷޙॾʑͷࣄ৘ΛצҊͯ͠AWS/Auroraʹࡌͤସ͑ͨɻ • ࣮૷্ͷӨڹൣғ͸͔֬ʹinfraҎԼʹݶఆ͞Εɺॻ͖׵͑ࣗମ͸2೔͔͔Βͳ͍͘Β͍ͰࡁΜͩɻ • (࣮૷͠ͳ͍Ͱࣄલͷݕ౼Ͱશ෦ચ͍ग़ͤΑɺͱݴΘΕΔͱ͕ࣖ௧͍ͱ͜ΖͰ͸͋Δ...·͊ૉৼΓ΋ͨ·ʹ͸Ͷ?)