gocon2016 spring 自作webフレームワーク uconを作った話
Post on 14-Apr-2017
2.849 Views
Preview:
TRANSCRIPT
自作Webフレームワーク uconを作った話
わかめ まさひろ
わかめ まさひろ @v vakame
TypeScript
Masahiro Wakame
DefinitelyTypedappengine/go
photo from golang.org/doc/gopher/
GoogleAppEngine/Go
神 いわゆる GOD
諸君、私はappengine/goが好きだ
• 2008年4月 始まる
• サーバレスアーキテクチャ
• 2011年5月 Go対応始まる
• 2015年7月 Go, GAになる
• 2016年4月 Go 1.6対応1.9.35→❌ 1.9.36を使おう!
Eric Schmidt said.
https://www.youtube.com/watch?v=HgWHeT_OwHc&t=1461GCP Next 2016 Day 1 Keynote
GAE用ライブラリ作ってます• testerator github.com/favclip/testrator
• UnitTest高速化
• qbg github.com/favclip/qbg
• Datastore用TypeSafeクエリビルダ
• smg github.com/favclip/smg
• Search API用TypeSafeラッパproductionで利用中!
GoCon 2015 Summer
• appengine専用じゃないけど
• jwg github.com/favclip/jwg
• genbase github.com/favclip/genbase
• GoCon 2015で話をしました!
• SlideShare goo.gl/45lZDK
gb
• gb
• getgb.io/
• gb keeps the peace of our project🌹
• gb gae
• github.com/PalmStoneGames/gb-gae
Google API Discovery Service
誰か知ってる?
APIs Explorer is 神
https://developers.google.com/apis-explorer/
APIs Explorer
• 誰でも簡単に使える
• 実際のAPIが叩かれる
• 結果を共有しやすい
• コードからUIが生成されている
Cloud Endpoints• appengine専用の仕組み
• cloud.google.com/endpoints/
• 自前APIでAPIs Explorer使える
• APIの構造がわかる!
• 実例がわかる!(DevTool)
• UIを省く極道管理画面も!
周辺ツールも充実• クライアントライブラリの自動生成
• golangだとこの辺全部そう
• github.com/google/google-api-go-client
• TypeScript用型定義の生成
• www.npmjs.com/package/gapidts
but…
Googleの闇の領域
ユーザ GAE闇
path mapping
request format
Version切替後reqをなかった事にcustom domain不可
go-endpoints• CloudEndpoints用framework
• github.com/GoogleCloudPlatform/go-endpoints
• Service & Method の組み合わせで定義
• w http.ResponseWriterが取れない
• CloudEndpoints的には不要なので…
評価• 細かい事を気にしなければかなり良い
• カスタムドメイン不可がやはり辛い
• デバッグ不可能な闇の領域が辛い
• ちょいちょいそこが不安定な気が…
• 拡張性が低い
• 横断的な処理を入れにくい…
僕達が必要なもの
Alt Cloud Endpoints• APIs Explorer的なものが欲しい!
• サバクラの意思疎通が楽• デバッグが楽
• コード→仕様が良い
• 仕様→コード は努力が必要(努力やだ• クライアントコードの生成
• 変わったら壊れてほしい
代替ツールの検討
✨swagger✨• Swaggerが一番良さそう!
• 個人の見解です
• Open API Initiative発足
• Swagger仕様をbaseに
• 長いものには巻かれたい
• Qiitaに少し書きました goo.gl/BLS3uH
既存GoなSwagger実装の話
最初からswagger対応の物を選ぶと楽そう
既存Go実装の比較• go-swagger
• goswagger.io/• type safeじゃない
• yvasiyarov/swagger• github.com/yvasiyarov/swagger• comment baseでつらい
• type safeじゃない
既存Go実装の比較• go-restful
• github.com/emicklei/go-restful/• type safeじゃない
• goa• goa.design/• type safeじゃない
• DSLがヤバイ
既存Go実装の比較
• grpc-gateway• github.com/gengo/grpc-gateway• gRPCのJSONなreverse proxy
• いつのまにかswagger対応してた
• appengineだと❌
既存Webフレームワーク
重要なポイント• net/httpに近いほうがわかりやすい
• あまりに独自っぽいのはちょっと…
• go-endpointsからの移行
• しばらくCloudEndpointsと両立したい
• コード上の互換性があると嬉しい…
• swagger-uiが使える
既存フレームワーク調べた• なるべくnet/httpに近い
• revel→❌• なるべくCloudEndpointsのまま
• net/http→❌• goji→❌
• Swagger対応!
• martini→❌
結論
•自分でつくろう
趣味に走ったわけではないです
自分で作る話
前提• appengine縛りにはしない
• とはいえappengineで使えないと困る
• net/httpに近いAPI
• 柔軟性
• go-endpointsとの互換性
• swaggerはopt-in やっていく
名付け親
某a2cさん
martiniとか
ginとかに 対抗して
名付け親
某a2cさん
uconと 名付けよう!
In japan, ucon (= turmeric) is to be effective in hangover.
決めた後
某a2cさん
💩 ←ゆるさない!!
仕様紹介
まず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) { … }
まず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
まず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
闇の領域でもろもろ変換されてる
ucon Features• net/http との類似性
• Routing• Method, Path Matching
• Middleware• Bubble• Dependency Injection
• Plugin
API likes net/http
ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {})
API likes net/http
ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {})
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}“, …
Routing rule• METHODが一致する
• * 指定も可 厳密一致優先• Request Pathが一致する
• 複数候補ある場合より長い節一致• Request GET /api/user/123
• 🌟 GET /api/user/{id}• ❌ GET /api/user
• 先登録優先
Middleware
• 1 request毎の処理に介入
• JavaでいうServletFilter
• ASP.NET MVCでいうFilter
• Logging, DI, CORS用Header, error→JSON変換 etc, etc…
Middleware
Middleware
Middleware
Middleware
Handler
ServeHTTP DI
Cache-ControlCookieappengine.Context
etc, etc…
CORS Header
Path, Query, Body → JSON
*http.Requesthttp.ResponseWriter
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() } }
built-in middleware
• HTTPRWDI
• *http.Request, http.ResponseWriterのDI
• NetContextDI
• net/contextのContextをDI
built-in middleware
• RequestObjectMapper
• path parameter, query paramter, post bodyをObjectに変換しDI
• ResponseMapper
• HandlerがreturnしたObjectやerrorをJSONに変換
Plugin• プロセス起動時1回だけ動作
• 全Handlerの走査
• Handler→Plugin間の値の伝達機構
• swaggerはplugin
• 全Handlerの情報から処理
• swagger.json出力用Handlerの追加
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)) } } }
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)) } } }
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) { … }
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) { … }
コード規模の話
• 本体 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
利用事例
利用サイト
• favclip
• 技術書典 応募サイト
• 怖くてOSSにでけんかったすまんな…
• Topgate社内では今後使っていくはず…
swagger関連ツール
• swagger-uiの話
• Go用クライアントライブラリの話
• TypeScript用型定義ファイル生成の話
求む!
求む!
• 利用してみてブログ書く
• 利用してみて質問する
• 利用してみて…
自分が使えるようになると 満足するタイプ
We are hiring
We are hiring 1
•開発:テレビ朝日
• jwg, genbase 他 必要に応じて製造
• http://www.favclip.com/
• appengine/go 開発者絶賛募集中!
We are hiring 2
• Topgate社も絶賛募集中です
• appengineできる人
• HTML, CSS, JS得意な人
•その他
雑談
https://github.com/golang/proposal/blob/master/design/15292-generics.md
👍for Web app
top related