Share
Embed
Go言語で作られたAPIのレガシーコードと戦った話 / Go Conference 2015 winter
Go言語で作られたAPIのレガシーコードと戦った話
kyokomi
December 06, 2015
Transcript
60 slides
-
ࣗݾհ • Twi%er:)@k_yokomi)ʢ͖ΐ͜Έʣ※1 • ࣄGoͰAPIॻ͍ͯ·͢(1͘Β͍) • ੲCocos2d<xͱ͔৮ͬͯ·ͨ͠ʢv3.4 ͘Β͍·Ͱʣ • ࠷ۙɺGAE/Go͕ؾʹೖ͍ͬͯΔ
※1"ΞΠίϯ݄ඣͷᘡᘚ͞ΜͰ͢ɻʢΓͷԂ༷"ఏڙʣ @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 2 -
աڈʹ࡞ͬͨͷհ1 • github.com/kyokomi/slackbot • slackઐ༻ͷGoͷhubot • ϓϥάΠϯՃ͍͢͠Α͏ʹͯ͠ΔͭΓ • github.com/kyokomi/emoji •
λʔϛφϧֆจࣈϥΠϒϥϦ • ૣ͍ऀউͪͰ࡞ͬͨײ͡ɻελʔ70͑ͨ7⭐ @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 3 -
աڈʹ࡞ͬͨͷհ2 • github.com/kyokomi/go/docomo/docomo • docomoͷAPIϥΠϒϥϦʢࡶஊɺࣝQ&Aͱ͔ରԠʣ • github.com/kyokomi/goma • DBΞΫηεϑϨʔϜϫʔΫతͳ(JavaͷdomaΠϯεύΠΞ) •
ҰԠɺখنͳϓϩμΫγϣϯͰͬͯΔ • ՝͕ଟ͘ɻݱঢ়ͷར༻Φεεϝ͠ͳ͍...=! @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 4 -
ฤूͯ͠فΔ!!!※3 ʮฤूͯ͠فΔʯɺطଘͷίʔυΛҙਂ͘ௐͳ͕Βमਖ਼͢Δํ๏Ͱ͢ɻ ͜ͷ߹ͷखॱΛ૾ͯ͠Έ·͠ΐ͏ɻ ·ͣεςοϓ͝ͱʹԿ͕ى͜Δ͔Λ໌֬ʹཧղ͠ɺίʔυมߋͷӨڹൣғΛѲ͠·͢ɻ ࣍ʹɺ࣌ؒΛ͔͚ͯҙਂ͘ίʔυΛॻ͖ɺίʔυมߋ͕ͯ͢ऴΘͬͨΒɺفΔؾ࣋ͪͰಈ͔ͯ͠Έ·͢ɻ ͦͷ݁Ռʮ͋Εʁ มߋՕॴେৎ͚ͩͲ……ผͷػೳ͕ಈ͔ͳ͘ͳͬͯ͠·ͬͨ……ʯɻ ʮҙΛΑΓଟ͔ͬͨ͘Βͱ͍ͬͯɺඞͣͦ͠Εʹൺྫͯ҆͠શੑ্͕͢ΔͱݶΒͳ͍ʯͱஶऀڧ͘ड़͍ͯ·͢ɻ طଘίʔυʹखΛೖΕΔࡍʹɺʮΓͷৼΔ͍Λม͑ͣʹࡁ·ͤΔ͜ͱ͕Ͱ͖Δ͔͕ॏཁʯͰ͕͢ɺҙਂ͍͚ͩͰɺ ଞͷ෦ʹӨڹ͕ٴΜͰ͍Δ͔Ͳ͏͔·Ͱ࠷ޙʹಈ͔ͯ͠Έͳ͍ͱ͔Γ·ͤΜɻ
ͲΜͳʹҙਂ͘ਐΊͯɺ݁ہʮฤूͯ͠فΔʯ͜ͱʹͳͬͯ͠·͍·͢ɻ ※3"CodeZineʮϨΨγʔίʔυվળΨΠυʯͷεεϝΑΓ"h+p:/ /codezine.jp/ar5cle/detail/4103 @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 16 -
อޢͯ͠ฤू͢Δ!!!※3 ͏1ͭͷʮอޢͯ͠มߋ͢ΔʯɺςετΛ༻ҙͨ͠͏͑ͰطଘͷίʔυΛमਖ਼͢Δํ๏Ͱ͢ɻ ίʔυͷมߋ͕͍͠ཧ༝ΛΑ͘ߟ͑ͯΈΔͱɺςετ͕͋Είʔυͷมߋ͕؆୯ʹͳΔͱ͍͏ɺ ҙ֎ͱ୯७ͳ͜ͱʹؾ͕͖ͭ·͢ɻ ͠ɺมߋ͍ͯ͠Δ࠷தʹ͏·͘มߋͰ͖͍ͯΔ͔Ͳ͏͔ɺ ଞͷ෦ʹӨڹ͕ٴΜͰ͍ͳ͍͔Ͳ͏͔Λ֬ೝ͢Δखஈ͕͋ͬͨͱͨ͠ΒͲ͏ͳΔͰ͠ΐ͏͔ɻ ͦͷ߹ɺগ͠มߋΛͯ͠ɺେৎ͔Ͳ͏͔Λ֬ೝͯ͠ɺ ·ͨগ͠มߋΛͯ͠ɺͱ͍͏࡞ۀΛ܁Γฦ͠ͳ͕ΒਐΊΔ͜ͱ͕ՄೳʹͳΓ·͢ɻ ※3"CodeZineʮϨΨγʔίʔυվળΨΠυʯͷεεϝΑΓ"h+p:/ /codezine.jp/ar5cle/detail/4103
@kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 17 -
ग़Φνײ͋Γ·͕͢... • ͋Δʹ͋Δ͕ɺtestίʔυ͔Β༷͕ಡΈऔΕͳ͍ • ϝιουΛݺͼͩͤΔͷΛ֬ೝ͢Δ͚͕ͩతʹͳ͍ͬͯΔ • Test͕௨Βͳͯ͘ίϝϯτΞτ͞Ε͍ͯͨΓ • ͦͦςετίʔυ͕ͳ͍ύοέʔδଟʑ͋Γ •
globalมΛ͋ͪͪ͜Ͱ͍ͬͯΔͨΊMock͕͍͠ @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 25 -
ରॲޙ ͺͬͱݟͰෆཁʹͳΓͦ͏ͳίʔυϦϑΝΫλϦϯάޙεϚʔ τʹςετॻ͚ͦ͏ɾɾɾͱ͍͏ࢥ͍Λ͙ͬͱת͑Δɻ • ແବʹͳΔ͔͠Εͳ͍͕Ұ୴ςετίʔυΛॻ͘ • ॻ͍ͨςετίʔυͰ༷Λຬ͍ͨͤͯΔ͔ෆ͕҆ͩفΔ • ϦϑΝΫλϦϯά͢Δ •
ෆཁͳͬͨςετίʔυΛফ͢ @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 26 -
ରॲޙʢίʔυྫʣ // ٖࣅίʔυͰ͢ʢ͜ΜͳΠϝʔδʣ data, err := AmazonS3.Get(bucketName, filePath) if err
!= nil { // airbrake͢Δ data, err = Local.Get("filePath") if err != nil { // ී௨ʹerror } } @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 31 -
errorΛѲΓͭͿͯ͠Δ data, _ := json.Marshal(resp.Body) if data == nil {
// ... } !!!!!!! @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 33 -
ରॲޙ ී௨ʹΤϥʔϋϯυϦϯάɻ߹ʹΑͬͯAirbrakeͰ௨ɻ data, err := json.Marshal(resp.Body) if err != nil
{ return fmt.Errorf("XXXXͷjsonͷMarshalͰࣦഊͯ͠Δ %s", err) } if data == nil { // ... } @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 34 -
gorou%neͷཚ༻ ฒߦॲཧͯ͠େৎͦ͏ͱ͍͏ཧ༝Ͱ ΊͪΌͪ͘Όgorou%neΛ͍ͬͯΔɻ WaitGroupͱChannelΛ͋ͪͪ͜Ͱͯ͠ ͍ͬͯͯɺͺͬͱݟԿ͍ͨ͠ͷ͔Θ͔ Β͵Ͱ͟͝ΔΑɾɾɾ ʢͦͦઃܭ͕͓͔͍͠ؾ͕͢Δʣ var wg sync.WaitGroup
dataCh := make(chan string) for _, t := range hogeList { // ... লུ ... go hoge(dataCh, &wg) for _, c := range t.Contents { // ... লུ ... wg.Add(1) go func() { source(dataCh, c) wg.Done() }() } } wg.Wait() close(dataCh) @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 36 -
ࣾڞ௨ϥΠϒϥϦతͳڞ௨ύοέʔδ ├── gocommon // ࣾڞ௨ϥΠϒϥϦͷϦϙδτϦ │ ├── airbrake // ࣾGoϓϩδΣΫτͰڞ௨ͷϥΠϒϥϦ
│ ├── aws │ ├── db │ ├── logger │ ├── xxxx // hoge-apiͰ͔͠Θͳ͍Α͏ͳڞ௨ॲཧ܈ │ ├── yyyy │ └── zzzz └── hoge-api // apiͷϦϙδτϦʢgocommonόϦόϦ͏ʣ ├── aaaa └── bbbb @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 40 -
APIґଘ͍ͯ͠ΔͷΛinternalԼʹҠಈͨ͠ ├── gocommon // ࣾڞ௨ϥΠϒϥϦͷϦϙδτϦ │ ├── airbrake // ࣾGoϓϩδΣΫτͰڞ௨ͷϥΠϒϥϦ
│ ├── aws │ ├── db │ └── logger └── hoge-api // apiͷϦϙδτϦ ├── aaaa // TODO: ͪ͜ΒinternalҠಈ༧ఆ ├── bbbb // TODO: ͪ͜ΒinternalҠಈ༧ఆ └── internal // apiͷڞ௨ϥΠϒϥϦ ├── xxxx ├── yyyy └── zzzz @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 41 -
ιʔείʔυ func goji2kami(gojiFn web.HandlerFunc) kami.HandlerFunc { return func(ctx context.Context, w
http.ResponseWriter, r *http.Request) { env := make(map[interface{}]interface{}) if di, ok := device.FromContext(ctx); ok { env["device"] = di } if u, ok := user.FromContext(ctx); ok { env["user"] = &models.User{ID: int(u.ID)} } else { env["user"] = (*models.User)(nil) } gojiFn(web.C{Env: env}, w, r) } } @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 45 -
ϦϑΝΫλϦϯάDay ຖि༵ۚʹͻͨ͢ΒϦϑΝΫλϦϯά͢ΔͱܾΊͨɻ աڈͷҨ࢈ྺ࢙తܦҢΛ࣌ͷϝϯόʔʹฉ͍ͨΓɺDBͷߏ ఆાஔͷҋΛͬͨΓɻ • ෆཁͳίʔυΛআ • ςετ͕ແ͍ՕॴͷςετՃ • ΉͪΌͪ͘ΌͳґଘΛ͍ͯ͠ΔίʔυͷґଘؔΛΓ͢
@kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 46 -
hhthಛ h"ptestͷhelperΫϥεతͳҐஔ͚ɻ h"ptestͰAPIΛݺͼग़ͨ݁͠ՌͷνΣοΫॲཧΛ͍·ΘͤΔɻ • StatusCode • ContentLength • jsonͷParse •
Response @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 52 -
package example_test import ( "net/http" "testing" "github.com/kyokomi/hhth" ) func TestHogeHandler(t
*testing.T) { hhtHelper := hhth.New(http.DefaultServeMux) hhtHelper.SetTestCase( hhth.TestCaseStatusCode(http.StatusOK), hhth.TestCaseContentType("text/plain; charset=utf-8"), ) resp := hhtHelper.Get("/hoge") if resp.Error() != nil { t.Errorf("error %s", resp.Error()) } if resp.String() != "hoge" { t.Errorf("error response body hoge != %s", resp.String()) } } @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 54 -
͍ํৄࡉ1 // HanderΛηοτͯ͠એݴ hhtHelper := hhth.New(http.DefaultServeMux) // TestCaseΛઃఆʢStatusCodeͱContentTypeͷνΣοΫʣ hhtHelper.SetTestCase( hhth.TestCaseStatusCode(http.StatusOK),
hhth.TestCaseContentType("text/plain; charset=utf-8"), ) @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 55 -
͍ํৄࡉ2 // GET /hogeΛςετ͢Δ resp := hhtHelper.Get("/hoge") // ઌ΄Ͳઃఆͨ͠handlerΛݺͼग़ͨ݁͠Ռ or
TestCaseͷΤϥʔ͕ฦͬͯ͘Δ if resp.Error() != nil { t.Errorf("error %s", resp.Error()) } @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 56 -
͍ํৄࡉ3 // CustomTestCaseΛՃʢJSONΛύʔεͯ͠தΛνΣοΫʣ hhtHelper.Get("/hoge", func(resp hhth.Response) error { var hoge
Hoge if err := resp.JSON(&hoge); err != nil { return fmt.Errorf("hoge parse error %s", err.Error()) } if hoge.Num() != 100 { return fmt.Errorf("hoge num 100 != %d", hoge.Num()) } return nil }) @kyokomi,(2015/12/06(ʮGo(Conference(2015(winterʯ 57