3 secrets to build web APIs in Go Matt Aimonetti - splice.com - @mattetti
Matt Aimonetti
golangbootcamp.com
splice: Go, Ruby, JS, Obj-C, C#, C++, C
Splice
Splice
Architecture clients
APIs / core logic
web rendering
Architecture Obj-C
C Go
Web API: Synchronization File upload File download Notifications
C#
Architecture
file parsing/analysis (sessions, media) deduplication/storage intermediate representation
Architecture
internal/private API (application logic)
“public” API (states retrieval/change)
Architecture
Message Bus HTTP
Long running async tasks
Go vs Ruby
20-100x faster to run 10-20% slower to write 60-80% less maintenance required batteries included
• • •
Deployment Routing Monitoring
Deployment
Zero downtime deployments bitbucket.org/splice/go.grace
g h t i w l u f care
s e n i t orou
Deployment
Version Control
CI
Storage
Deployment
Deployment
current/api
symlink
2014-06-23-183000/api
2014-06-23-204500/api
Deployment
current/api
symlink
2014-06-23-183000/api
2014-06-23-204500/api
$ kill -USR2 {api PID}
Routing var V42Routes = Endpoints{ // create Ableton Live session { Verb: "POST", Path: "/v42/live/sessions", Handler: handlers.AlsUpload, RequiredParams: handlers.AlsUploadReqParams, Auth: splice.ClientAuth, }, // create Logic session { Verb: "POST", Path: "/v42/logic/sessions", Handler: handlers.LogicUpload, RequiredParams: handlers.LogicUploadReqParams, Auth: splice.ClientAuth, }, }
Routing
package router type Endpoint struct { Verb string Path string RequiredParams []string Auth func(*splice.ReqEnv, bool) bool Handler splice.Handler }
Routing type Endpoints []Endpoint // Activate all the endpoint API routes.
func (es Endpoints) Activate(p *pat.PatternServeMux) { for _, e := range es { // [..] set authentication calls // [..] check param requirements
handler := splice.ReqWrap(e.Handler, e.Auth) p.Add(e.Verb, e.Path, handler) // [..] collect all CORS verbs accepted for this endpoint so we can create OPTIONS routes.
} }
Routing func ReqWrap(handler *Handler, checkAuthFn func(*ReqEnv, bool) bool) http.Handler { ! return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() defer reportRequest(req, start, handler.Metrics) reqUuid := uuid.GenUUID() req := &ReqEnv{ ReqUuid: reqUuid, Request: r, Response: &statusResponseWriter{w, 0}, } // [..] set headers (content type, req ID, build) if !checkAuthFn(req, checkAuth) { return }
! ! !
Routing ! ! !
// We must return a 400 and stop here if there was a problem // parsing the request. err := req.ParseParams() if err != nil { // […]return 400 return } // convert a panic into a 500 defer req.handlePanic() handler.Handle(req) })
}
Monitoring
Monitor all the things!
runtime.ReadMemStats http://golang.org/pkg/runtime/#MemStats
Lessons learned
low memory usage
Lessons learned
detect issues
Lessons learned
detect issues
Low memory usage
use streaming io.Reader/io.Writer io.Copy
Encoder/Decoder instead of ReadAll and Unmarshal
Monitoring
Monitor all the things! Per endpoint: • # of requests • Status codes • Response times
@mattetti http://matt.aimonetti.net