gocon2016 spring 自作webフレームワーク uconを作った話

67
自作Webフレームワーク uconを作った話 わかめ まさひろ

Upload: masahiro-wakame

Post on 14-Apr-2017

2.850 views

Category:

Technology


4 download

TRANSCRIPT

Page 1: GoCon2016 spring 自作Webフレームワーク uconを作った話

自作Webフレームワーク uconを作った話

わかめ まさひろ

Page 2: GoCon2016 spring 自作Webフレームワーク uconを作った話

わかめ まさひろ @v vakame

TypeScript

Masahiro Wakame

DefinitelyTypedappengine/go

photo from golang.org/doc/gopher/

Page 3: GoCon2016 spring 自作Webフレームワーク uconを作った話

GoogleAppEngine/Go

神 いわゆる GOD

Page 4: GoCon2016 spring 自作Webフレームワーク uconを作った話

諸君、私はappengine/goが好きだ

• 2008年4月 始まる

• サーバレスアーキテクチャ

• 2011年5月 Go対応始まる

• 2015年7月 Go, GAになる

• 2016年4月 Go 1.6対応1.9.35→❌ 1.9.36を使おう!

Page 5: GoCon2016 spring 自作Webフレームワーク uconを作った話

Eric Schmidt said.

https://www.youtube.com/watch?v=HgWHeT_OwHc&t=1461GCP Next 2016 Day 1 Keynote

Page 6: GoCon2016 spring 自作Webフレームワーク uconを作った話

GAE用ライブラリ作ってます• testerator github.com/favclip/testrator

• UnitTest高速化

• qbg github.com/favclip/qbg

• Datastore用TypeSafeクエリビルダ

• smg github.com/favclip/smg

• Search API用TypeSafeラッパproductionで利用中!

Page 7: GoCon2016 spring 自作Webフレームワーク uconを作った話

GoCon 2015 Summer

• appengine専用じゃないけど

• jwg github.com/favclip/jwg

• genbase github.com/favclip/genbase

• GoCon 2015で話をしました!

• SlideShare goo.gl/45lZDK

Page 8: GoCon2016 spring 自作Webフレームワーク uconを作った話

gb

• gb

• getgb.io/

• gb keeps the peace of our project🌹

• gb gae

• github.com/PalmStoneGames/gb-gae

Page 9: GoCon2016 spring 自作Webフレームワーク uconを作った話

Google API Discovery Service

誰か知ってる?

Page 10: GoCon2016 spring 自作Webフレームワーク uconを作った話

APIs Explorer is 神

https://developers.google.com/apis-explorer/

Page 11: GoCon2016 spring 自作Webフレームワーク uconを作った話

APIs Explorer

• 誰でも簡単に使える

• 実際のAPIが叩かれる

• 結果を共有しやすい

• コードからUIが生成されている

Page 12: GoCon2016 spring 自作Webフレームワーク uconを作った話

Cloud Endpoints• appengine専用の仕組み

• cloud.google.com/endpoints/

• 自前APIでAPIs Explorer使える

• APIの構造がわかる!

• 実例がわかる!(DevTool)

• UIを省く極道管理画面も!

Page 13: GoCon2016 spring 自作Webフレームワーク uconを作った話

周辺ツールも充実• クライアントライブラリの自動生成

• golangだとこの辺全部そう

• github.com/google/google-api-go-client

• TypeScript用型定義の生成

• www.npmjs.com/package/gapidts

Page 14: GoCon2016 spring 自作Webフレームワーク uconを作った話

but…

Page 15: GoCon2016 spring 自作Webフレームワーク uconを作った話

Googleの闇の領域

ユーザ GAE闇

path mapping

request format

Version切替後reqをなかった事にcustom domain不可

Page 16: GoCon2016 spring 自作Webフレームワーク uconを作った話

go-endpoints• CloudEndpoints用framework

• github.com/GoogleCloudPlatform/go-endpoints

• Service & Method の組み合わせで定義

• w http.ResponseWriterが取れない

• CloudEndpoints的には不要なので…

Page 17: GoCon2016 spring 自作Webフレームワーク uconを作った話

評価• 細かい事を気にしなければかなり良い

• カスタムドメイン不可がやはり辛い

• デバッグ不可能な闇の領域が辛い

• ちょいちょいそこが不安定な気が…

• 拡張性が低い

• 横断的な処理を入れにくい…

Page 18: GoCon2016 spring 自作Webフレームワーク uconを作った話

僕達が必要なもの

Page 19: GoCon2016 spring 自作Webフレームワーク uconを作った話

Alt Cloud Endpoints• APIs Explorer的なものが欲しい!

• サバクラの意思疎通が楽• デバッグが楽

• コード→仕様が良い

• 仕様→コード は努力が必要(努力やだ• クライアントコードの生成

• 変わったら壊れてほしい

Page 20: GoCon2016 spring 自作Webフレームワーク uconを作った話

代替ツールの検討

Page 21: GoCon2016 spring 自作Webフレームワーク uconを作った話

各ツールの評価

• RAML

• API Blueprint (apiary.io

• JSON Schema v4

• Swagger

• gRPC

Page 22: GoCon2016 spring 自作Webフレームワーク uconを作った話

✨swagger✨• Swaggerが一番良さそう!

• 個人の見解です

• Open API Initiative発足

• Swagger仕様をbaseに

• 長いものには巻かれたい

• Qiitaに少し書きました goo.gl/BLS3uH

Page 23: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存GoなSwagger実装の話

最初からswagger対応の物を選ぶと楽そう

Page 24: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存Go実装の比較• go-swagger

• goswagger.io/• type safeじゃない

• yvasiyarov/swagger• github.com/yvasiyarov/swagger• comment baseでつらい

• type safeじゃない

Page 25: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存Go実装の比較• go-restful

• github.com/emicklei/go-restful/• type safeじゃない

• goa• goa.design/• type safeじゃない

• DSLがヤバイ

Page 26: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存Go実装の比較

• grpc-gateway• github.com/gengo/grpc-gateway• gRPCのJSONなreverse proxy

• いつのまにかswagger対応してた

• appengineだと❌

Page 27: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存Webフレームワーク

Page 28: GoCon2016 spring 自作Webフレームワーク uconを作った話

重要なポイント• net/httpに近いほうがわかりやすい

• あまりに独自っぽいのはちょっと…

• go-endpointsからの移行

• しばらくCloudEndpointsと両立したい

• コード上の互換性があると嬉しい…

• swagger-uiが使える

Page 29: GoCon2016 spring 自作Webフレームワーク uconを作った話

既存フレームワーク調べた• なるべくnet/httpに近い

• revel→❌• なるべくCloudEndpointsのまま

• net/http→❌• goji→❌

• Swagger対応!

• martini→❌

Page 30: GoCon2016 spring 自作Webフレームワーク uconを作った話

結論

•自分でつくろう

趣味に走ったわけではないです

Page 31: GoCon2016 spring 自作Webフレームワーク uconを作った話

自分で作る話

Page 32: GoCon2016 spring 自作Webフレームワーク uconを作った話

前提• appengine縛りにはしない

• とはいえappengineで使えないと困る

• net/httpに近いAPI

• 柔軟性

• go-endpointsとの互換性

• swaggerはopt-in やっていく

Page 33: GoCon2016 spring 自作Webフレームワーク uconを作った話

ucon

https://github.com/favclip/ucon

Page 34: GoCon2016 spring 自作Webフレームワーク uconを作った話

名付け親

某a2cさん

martiniとか

ginとかに 対抗して

Page 35: GoCon2016 spring 自作Webフレームワーク uconを作った話

名付け親

某a2cさん

uconと 名付けよう!

In japan, ucon (= turmeric) is to be effective in hangover.

Page 36: GoCon2016 spring 自作Webフレームワーク uconを作った話

決めた後

某a2cさん

💩 ←ゆるさない!!

Page 37: GoCon2016 spring 自作Webフレームワーク uconを作った話

仕様紹介

Page 38: GoCon2016 spring 自作Webフレームワーク uconを作った話

まずgo-endpointss := &fooService{}api, err := endpoints.RegisterService(s, "foo", "v1", "Foo API", true)if err != nil { panic(err.Error()) } info := api.MethodByName("Get").Info()info.HTTPMethod, info.Path, info.Desc = “GET", "/foo/{id}", “Fooを1件取得する"

type IntIDRequest struct { ID int64 `json:"id,string" ̀}

func (s *fooService) Get(r *http.Request, req *IntIDRequest) (*FooJSON, error) { … }

Page 39: GoCon2016 spring 自作Webフレームワーク uconを作った話

まずgo-endpointss := &fooService{}api, err := endpoints.RegisterService(s, "foo", "v1", "Foo API", true)if err != nil { panic(err.Error()) } info := api.MethodByName("Get").Info()info.HTTPMethod, info.Path, info.Desc = “GET", "/foo/{id}", “Fooを1件取得する"

type IntIDRequest struct { ID int64 `json:"id,string" ̀}

func (s *fooService) Get(r *http.Request, req *IntIDRequest) (*FooJSON, error) { … } Handler

Response

Setup

Page 40: GoCon2016 spring 自作Webフレームワーク uconを作った話

まずgo-endpointss := &fooService{}api, err := endpoints.RegisterService(s, "foo", "v1", "Foo API", true)if err != nil { panic(err.Error()) } info := api.MethodByName("Get").Info()info.HTTPMethod, info.Path, info.Desc = “GET", "/foo/{id}", “Fooを1件取得する"

type IntIDRequest struct { ID int64 `json:"id,string" ̀}

func (s *fooService) Get(r *http.Request, req *IntIDRequest) (*FooJSON, error) { … }

Request

Response

闇の領域でもろもろ変換されてる

Page 41: GoCon2016 spring 自作Webフレームワーク uconを作った話

ucon Features• net/http との類似性

• Routing• Method, Path Matching

• Middleware• Bubble• Dependency Injection

• Plugin

Page 42: GoCon2016 spring 自作Webフレームワーク uconを作った話

API likes net/http

ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {})

Page 43: GoCon2016 spring 自作Webフレームワーク uconを作った話

API likes net/http

ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {})

Page 44: GoCon2016 spring 自作Webフレームワーク uconを作った話

Routing

ucon.HandleFunc(“*", “/“, … ucon.HandleFunc(“OPTIONS", “/“, … ucon.HandleFunc(“GET", “/“, … ucon.HandleFunc(“POST", “/“, … ucon.HandleFunc(“GET", “/api/user“, … ucon.HandleFunc(“GET", “/api/user/me“, … ucon.HandleFunc(“GET", “/api/user/{id}“, …

Page 45: GoCon2016 spring 自作Webフレームワーク uconを作った話

Routing rule• METHODが一致する

• * 指定も可 厳密一致優先• Request Pathが一致する

• 複数候補ある場合より長い節一致• Request GET /api/user/123

• 🌟 GET /api/user/{id}• ❌ GET /api/user

• 先登録優先

Page 46: GoCon2016 spring 自作Webフレームワーク uconを作った話

Middleware

• 1 request毎の処理に介入

• JavaでいうServletFilter

• ASP.NET MVCでいうFilter

• Logging, DI, CORS用Header, error→JSON変換 etc, etc…

Page 47: GoCon2016 spring 自作Webフレームワーク uconを作った話

Middleware

Middleware

Middleware

Middleware

Handler

ServeHTTP DI

Cache-ControlCookieappengine.Context

etc, etc…

CORS Header

Path, Query, Body → JSON

*http.Requesthttp.ResponseWriter

Page 48: GoCon2016 spring 自作Webフレームワーク uconを作った話

Middlewaretype MiddlewareFunc func(b *Bubble) error

type Bubble struct { R *http.Request W http.ResponseWriter Context context.Context RequestHandler interface{} ArgumentTypes []reflect.Type Arguments []reflect.Value Returns []reflect.Value }

func (b *Bubble) Next() error { … }

func (b *Bubble) do() error { hv := reflect.ValueOf(b.handler())

… b.Returns = hv.Call(b.Arguments) return nil}

var httpReqType = reflect.TypeOf((*http.Request)(nil))var httpRespType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()

func HTTPRWDI() MiddlewareFunc { return func(b *Bubble) error { for idx, argT := range b.ArgumentTypes { if argT == httpReqType { b.Arguments[idx] = reflect.ValueOf(b.R) continue } if argT == httpRespType { b.Arguments[idx] = reflect.ValueOf(b.W) continue } } return b.Next() } }

Page 49: GoCon2016 spring 自作Webフレームワーク uconを作った話

built-in middleware

• HTTPRWDI

• *http.Request, http.ResponseWriterのDI

• NetContextDI

• net/contextのContextをDI

Page 50: GoCon2016 spring 自作Webフレームワーク uconを作った話

built-in middleware

• RequestObjectMapper

• path parameter, query paramter, post bodyをObjectに変換しDI

• ResponseMapper

• HandlerがreturnしたObjectやerrorをJSONに変換

Page 51: GoCon2016 spring 自作Webフレームワーク uconを作った話

Plugin• プロセス起動時1回だけ動作

• 全Handlerの走査

• Handler→Plugin間の値の伝達機構

• swaggerはplugin

• 全Handlerの情報から処理

• swagger.json出力用Handlerの追加

Page 52: GoCon2016 spring 自作Webフレームワーク uconを作った話

Plugintype pluginContainer struct { base interface{}} type HandlersScannerPlugin interface { HandlersScannerProcess(m *ServeMux, rds []*RouteDefinition) error }

type RouteDefinition struct { Method string PathTemplate *PathTemplate HandlerContainer HandlerContainer }

func (m *ServeMux) Prepare() { for _, plugin := range m.plugins { used := false if sc := plugin.HandlersScanner(); sc != nil { err := sc.HandlersScannerProcess(m, m.router.handlers) if err != nil { panic(err) } used = true } if !used { panic(fmt.Sprintf("unused plugin: %#v", plugin)) } } }

Page 53: GoCon2016 spring 自作Webフレームワーク uconを作った話

Plugintype pluginContainer struct { base interface{}} type HandlersScannerPlugin interface { HandlersScannerProcess(m *ServeMux, rds []*RouteDefinition) error }

type RouteDefinition struct { Method string PathTemplate *PathTemplate HandlerContainer HandlerContainer }

func (m *ServeMux) Prepare() { for _, plugin := range m.plugins { used := false if sc := plugin.HandlersScanner(); sc != nil { err := sc.HandlersScannerProcess(m, m.router.handlers) if err != nil { panic(err) } used = true } if !used { panic(fmt.Sprintf("unused plugin: %#v", plugin)) } } }

Page 54: GoCon2016 spring 自作Webフレームワーク uconを作った話

swagger plugin usageswPlugin := swagger.NewPlugin(…) ucon.Plugin(swPlugin)

s := &fooService{}tag := swPlugin.AddTag(&swagger.Tag{Name: "Foo", Description: ""})var info *swagger.HandlerInfoinfo = swagger.NewHandlerInfo(s.List)ucon.Handle("GET", "/api/foo/{id}", info)info.Description, info.Tags = "Fooを1件取得する", []string{tag.Name} …

type IntIDRequest struct { ID int64 `json:"id,string" ̀}

func (s *fooService) Get(r *http.Request, req *IntIDRequest) (*FooJSON, error) { … }

Page 55: GoCon2016 spring 自作Webフレームワーク uconを作った話

go-endpoints(再掲s := &fooService{}api, err := endpoints.RegisterService(s, "foo", "v1", "Foo API", true)if err != nil { panic(err.Error()) } info := api.MethodByName("Get").Info()info.HTTPMethod, info.Path, info.Desc = “GET", "/foo/{id}", “Fooを1件取得する"

type IntIDRequest struct { ID int64 `json:"id,string" ̀}

func (s *fooService) Get(r *http.Request, req *IntIDRequest) (*FooJSON, error) { … }

Page 56: GoCon2016 spring 自作Webフレームワーク uconを作った話

コード規模の話

• 本体 1329行

• ls | grep .go | grep -v _test.go | xargs wc -l

• swaggerプラグイン 1138行• find ./swagger -type f | grep .go | grep -v sample | grep -v _test.go | xargs wc -l

Page 57: GoCon2016 spring 自作Webフレームワーク uconを作った話

利用事例

Page 58: GoCon2016 spring 自作Webフレームワーク uconを作った話

利用サイト

• favclip

• 技術書典 応募サイト

• 怖くてOSSにでけんかったすまんな…

• Topgate社内では今後使っていくはず…

Page 59: GoCon2016 spring 自作Webフレームワーク uconを作った話

swagger関連ツール

• swagger-uiの話

• Go用クライアントライブラリの話

• TypeScript用型定義ファイル生成の話

Page 60: GoCon2016 spring 自作Webフレームワーク uconを作った話

求む!

Page 61: GoCon2016 spring 自作Webフレームワーク uconを作った話

求む!

• 利用してみてブログ書く

• 利用してみて質問する

• 利用してみて…

自分が使えるようになると 満足するタイプ

Page 62: GoCon2016 spring 自作Webフレームワーク uconを作った話

We are hiring

Page 63: GoCon2016 spring 自作Webフレームワーク uconを作った話

We are hiring 1

•開発:テレビ朝日

• jwg, genbase 他 必要に応じて製造

• http://www.favclip.com/

• appengine/go 開発者絶賛募集中!

Page 64: GoCon2016 spring 自作Webフレームワーク uconを作った話

We are hiring 2

• Topgate社も絶賛募集中です

• appengineできる人

• HTML, CSS, JS得意な人

•その他

Page 65: GoCon2016 spring 自作Webフレームワーク uconを作った話

雑談

Page 66: GoCon2016 spring 自作Webフレームワーク uconを作った話

https://github.com/golang/proposal/blob/master/design/15292-generics.md

Page 67: GoCon2016 spring 自作Webフレームワーク uconを作った話

👍for Web app