ãã®è¨äºã¯ãLayerX Tech Advent Calendar 2024 ã®8æ¥ç®ã®è¨äºã§ãã
ãã¯ã©ã¯ãã¸ãã¹ã«ã¼ãéçºãã¼ã Tech Lead ã® budougumi0617 ã§ãã
ä»åã¯Goã§Webã¢ããªã±ã¼ã·ã§ã³ãä½ãéã«å©ç¨ããHTTPããã«ã¦ã§ã¢ã§ãªã¯ã¨ã¹ããã°ãåºåããéã®Tipsã§ãã
HTTPãã³ãã©ã¼å
ã®ãã¸ãã¹ãã¸ãã¯ã§åå¾ã»å°åºããæ
å ±ããªã¯ã¨ã¹ãã«ä»ä¸ããæ¹æ³ã解説ãã¾ãã
TL;DR
Goè¨èªã§Webã¢ããªã±ã¼ã·ã§ã³ãéçºããéãããããææ³ã¨ãã¦ããã«ã¦ã§ã¢ã§ã¦ã¼ã¶ã¼IDãããã³ãIDã¨ãã£ãæ
å ±ãæã£ããªã¯ã¨ã¹ããã°ï¼ã¢ã¯ã»ã¹ãã°ï¼ãåºåãã¾ãã
大æµã®å ´åã¯ãªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢ã®å段ã«ãã¾ããèªè¨¼ããã«ã¦ã§ã¢ã§åå¾ããæ
å ±ã使ã£ã¦ãªã¯ã¨ã¹ããã°ã«IDæ
å ±ãä»ä¸ãã¾ãããå¤é¨ãµã¼ãã¹ã«å
¬éããWebhookã®ãããªå ´åãèªè¨¼æ
å ±ãåå¾ã§ããªããã¨ãããã¾ãã
æ¬è¨äºã§ã¯ããã®ãããªã±ã¼ã¹ã§ããã¸ãã¹ãã¸ãã¯ä¸ï¼HTTPãã³ãã©ã¼å
ã®å®ãã¸ãã¯ï¼ã§åå¾ããæ
å ±ããªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢ã«æ¸¡ãæ¹æ³ã2ã¤ç´¹ä»ãã¾ãã
http.ResponseWriter
ãã©ããããcontext.Context
ã¸ã®ãã¤ã³ã¿ãªãã¸ã§ã¯ããæ ¼ç´ãã
ã©ã¡ãããã¡ãªããï¼æ¸å¿µäºé ï¼ããããããç¨æ³ç¨éãå®ã£ã¦å©ç¨ããå¿ è¦ãããã¾ãã
課é¡æ: Datadogã§ãã£ã«ã¿ã¼ãä½æãããããã°åæãè¡ãä¸ã§ãã¹ã¦ã®ãªã¯ã¨ã¹ããã°ã«ããã³ãIDãã¦ã¼ã¶ã¼IDã®æ å ±ãå«ããã
å¼ç¤¾ã§ã¯Datadogãç¨ãã¦æ¬çªç°å¢ã®ç£è¦ãè¡ã£ã¦ãã¾ããã¾ãããã°ã¯DWHã«æ ¼ç´ãã¦SnowflakeãLooker Studioããã®åæã§å©ç¨ãã¦ãã¾ãã
é常ã®ï¼èªè¨¼æ¸ã¿ã¦ã¼ã¶ã¼ã«ããï¼ãªã¯ã¨ã¹ãã§ã¯ãèªè¨¼ããã«ã¦ã§ã¢ã§ããã³ãIDãã¦ã¼ã¶ã¼IDãç¹å®ããcontext.Context
ãªãã¸ã§ã¯ãã«è©°ãè¾¼ãã ä¸ã§ãªã¯ã¨ã¹ããã°åºåæã«å©ç¨ãã¦ãã¾ã 1ã
Webhookç¨ã¨ã³ããã¤ã³ãã®ãããªç¹æ®ã±ã¼ã¹ã§ã¯ããã«ã¦ã§ã¢å±¤ã§IDãåå¾ã§ããªããã¨ããã
ç§ãæå±ãã¦ããã«ã¼ããã¼ã ã®APIãµã¼ãã«ã¯ãå¤é¨ãµã¼ãã¹ããã®Webhookãåãåãã¨ã³ããã¤ã³ããããã¤ãããã¾ãã
å
·ä½çãªå
容ã¯å²æãã¾ãããèå³ããããã㯠@shnjtk ã®ä»¥ä¸ã®è³æãããããã ããã
speakerdeck.com
ãããWebhookã®ãªã¯ã¨ã¹ãã¯æ±ºæ¸ãªã¯ã¨ã¹ããªã©ã大æµãããããã®ããã³ãã«ç´ã¥ããªã¯ã¨ã¹ãã§ã¯ããã¾ããããããä»ç¤¾ããåãã¨ã£ããªã¯ã¨ã¹ãã®ãããèªç¤¾ã®èªè¨¼åºç¤ã®ãçºè¡ããèªè¨¼æ
å ±ã¯å«ã¾ãã¦ãã¾ããã
ã¾ããä»ç¤¾ãã¼ã¹ã®æ
å ±ã§ããããããªã¯ã¨ã¹ãããã£ã®ä¸èº«ãåç´ã«è¦ãã ãã§ã¯ããã³ãIDãªã©ã¯ãããã¾ããï¼ä»ç¤¾ãçºè¡ããIDãå
ã«å¼ç¤¾DBããããã³ãæ
å ±ãæ¢ããªã©ã®éç¨ãå¿
è¦ï¼ã
ã¤ã¾ãã次ã§èª¬æããããã«ãªã¼ã½ããã¯ã¹ãªããã«ã¦ã§ã¢ã®å¦çã®æµãã®ä¸ã§ã¯IDãåå¾ã§ããããªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢ã®æ®µéã§context.Context
ã«IDãå
¥ã£ã¦ããåæãå´©ãã¦ãã¾ãã¾ãã
ããã³ãIDãç¡ããªã¯ã¨ã¹ããã°ã«ãã£ã¦ã¢ã©ã¼ãéç¨ã«æ¯éãåºã¦ãã
ããã³ãIDãªãã®ãªã¯ã¨ã¹ããã°ã§ããå¾ãããªã¯ã¨ã¹ãIDã§è¿½è·¡ããã°ä»ã®ãã°åºåã¨çµã¿åããã¦ããã³ãç¹å®ã¯å¯è½ã§ãã
ããããDatadogä¸ã®ã¢ã©ã¼ãè¨å®ãªã©ã§ããã³ãIDãæ¡ä»¶ã«ä½¿ã£ã¦ããã±ã¼ã¹ã§ã¯ãããã³ãIDããªããã°ãåå¨ãããã¨ã¯éç¨ä¸ä¸ä¾¿ã§ãã
ããããç¶æ³ã§ããã¸ãã¹ãã¸ãã¯ä¸ã§å°åºããã¦ã¼ã¶ã¼IDãããã³ãIDãªã©ãã©ãã«ããã¦ãªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢ã«æ¸¡ããããªã¨èãã¾ããã
ãããã: é常ã®HTTPããã«ã¦ã§ã¢ã«ããããªã¯ã¨ã¹ããã°åºåã¨IDæ å ±ä»ä¸ã®æµã
åè¿°ã®èª²é¡ã«å¯¾ããã¢ããã¼ããç´¹ä»ããåã«ããªã¼ã½ããã¯ã¹ãªèªè¨¼ããã«ã¦ã§ã¢ã¨ãªã¯ã¨ã¹ããã°ã®çµã¿åããããããããã¦ããã¾ãã
Goã§HTTP APIãµã¼ããä½æããæã¯http.Handler
ã¤ã³ã¿ã¼ãã§ã¼ã¹ãã¼ã¹ã§åã¨ã³ããã¤ã³ããå®è£
ãã¾ãã
èªè¨¼æ
å ±ã®ç¢ºèªãã¨ã©ã¼ãã³ããªã³ã°ããªã¯ã¨ã¹ããã°ã®åºåã¨ãã£ãå
±éæ©è½ã«ã¤ãã¦ã¯ãå©ç¨ãããã¬ã¼ã ã¯ã¼ã¯ã«ãã£ã¦å¤å°ã®å·®ç°ã¯ããã¾ããã
func (next http.Handler) http.Handler
å½¢å¼ã®ããã«ã¦ã§ã¢ã使ã£ã¦å®è£
ãããã¨ã«ãªãã§ãããã
ããã¦ããã«ãããã³ãSasSã§ç¨ããAPIãµã¼ãã®ãªã¯ã¨ã¹ããã°ã§ã¯ãã¦ã¼ã¶ã¼IDãããã³ãIDãªã©ã®æ
å ±ãå«ãããã¨ãä¸è¬çã§ãã
èªè¨¼æ¸ã¿ãªã¯ã¨ã¹ãã«å¯¾ãã¦ãä¸è¬çãªæ§æã§ã¯ä»¥ä¸ã®ãããªããã¼ã§ãªã¯ã¨ã¹ããã°ã«IDæ
å ±ãä»ä¸ãã¾ãã
- èªè¨¼ããã«ã¦ã§ã¢
- ãªã¯ã¨ã¹ãããããCookieãã»ãã·ã§ã³ãªã©ããèªè¨¼æ
å ±ãåå¾ããã¦ã¼ã¶ã¼IDãããã³ãIDã
http.Request
ãªãã¸ã§ã¯ãã«WithContext
ã¡ã½ãããããã¯Clone
ã¡ã½ããã§åãè¾¼ãã
- ãªã¯ã¨ã¹ãããããCookieãã»ãã·ã§ã³ãªã©ããèªè¨¼æ
å ±ãåå¾ããã¦ã¼ã¶ã¼IDãããã³ãIDã
- ãªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢
- å¾ç¶ã®å¦çãå®è¡å¾ã
Context
ãªãã¸ã§ã¯ãããIDæ å ±ãåãåºãããªã¯ã¨ã¹ããã°ã¸åºåãã¾ãã
- å¾ç¶ã®å¦çãå®è¡å¾ã
å ·ä½çãªå®è£ ã¨ãã¦ã¯æ¬¡ã®ãããªå½¢ã«ãªãã¾ãã
https://go.dev/play/p/ey5kGda4O7P
type userIDKey struct{} type tenantIDKey struct{} func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, tenantID := authenticate(r) // ãªã¯ã¨ã¹ããã¼ã¿ããèªè¨¼æ å ±ãåå¾ ctx := context.WithValue(r.Context(), userIDKey{}, userID) ctx = context.WithValue(ctx, tenantIDKey{}, tenantID) next.ServeHTTP(w, r.WithContext(ctx)) }) } func RequestLogMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) duration := time.Since(start) ctx := r.Context() userID := ctx.Value(userIDKey{}) tenantID := ctx.Value(tenantIDKey{}) log.Printf("Request log user=%v tenant=%v duration=%v\n", userID, tenantID, duration) }) } // AuthMiddleware(RequestLogMiddleware(businessLogicHandler)) ã¨ããå½¢ã§ããã«ã¦ã§ã¢ãçµã¿åããã
ãã°ã¤ã³æ¸ã¦ã¼ã¶ã¼ãå®è¡ããèªè¨¼æ¸ã®ãªã¯ã¨ã¹ãã«å¯¾ãã¦ã¯ãã®æ§æã§åé¡ãªããªã¯ã¨ã¹ããã°ã«IDæ å ±ãä»ä¸ã§ãã¾ãã
ããã§åé¡ã«ãªãã®ã次ã®åæç¥èã§ãã
åæç¥è1: Goã®context.Context
ã¯ã¤ãã¥ã¼ã¿ãã«
context.Context
ã¯ã¤ãã¥ã¼ã¿ãã«ã§ããWithValue
ã§æ°ããªãã¼ã¨å¤ã®ãã¢ãä»ä¸ããã¨æ°ããContext
ãªãã¸ã§ã¯ããè¿ãã¾ãããæ¢åContext
ãªãã¸ã§ã¯ããç´æ¥æ¸ãæãããã¨ã¯ã§ãã¾ããã
ã¤ã¾ãããã³ãã©ã¼å
é¨ã§Context
ãªãã¸ã§ã¯ãã«å¤ãå
¥ããã¨ãã¦ããå¼ã³åºãå
ã®Context
ãªãã¸ã§ã¯ãã¯é常å¤æ´ããã¾ããã
ã¾ãããã³ãã©ã¼å
é¨ã§Context
ãªãã¸ã§ã¯ãããValue
ã¡ã½ããã§åãåºããå¤ãå¤æ´ãã¦ãå
ã®Context
ãªãã¸ã§ã¯ãã«ã¯åæ ããã¾ããã
https://go.dev/play/p/181CW1UrxO3
type tenantIDKey struct{} func main() { orgTenantID := "original" ctx := context.WithValue(context.Background(), tenantIDKey{}, orgTenantID) tenantID := ctx.Value(tenantIDKey{}).(string) tenantID = "update" fmt.Printf("change %q\n", tenantID) // change "update" fmt.Printf("result %q\n", ctx.Value(tenantIDKey{}).(string)) //result "original" }
åæç¥è2: *http.Request
çµç±ã§ã¯è¦ªé¢æ°(å¼ã³åºãå
)ã¸Context
ãæ»ããªã
*http.Request
ãªãã¸ã§ã¯ããå
å
ããContext
ãªãã¸ã§ã¯ããä¸æ¸ããããã¨ã¯ã§ãã¾ããã
WithContext
ã¡ã½ãããå¼ã¶ã¨æ°ãã*http.Request
ãªãã¸ã§ã¯ããè¿ã£ã¦ãã¾ãããå¼ã³åºãå
ã®ããã«ã¦ã§ã¢ã«æ°ãã*http.Request
ãªãã¸ã§ã¯ããæ»ããã¨ã¯ã§ãã¾ããã
ã¤ã¾ãã以ä¸ã®ãããªã³ã¼ãã¯ãã¾ãããã¾ããã
func handler(w http.ResponseWriter, r *http.Request) { userID := getUserIDFromRequestBody(r) // 親ã®Contextã«å¤ã追å ãã¦ããããã§ã¯ãªã newCtx := context.WithValue(r.Context(), "userID", userID) // æ°ãã*http.Requestãä½æãã¦ãããã親ã®*http.Requestã«ã¯å½±é¿ããªã newReq := r.WithContext(newCtx) }
ãããã®åæç¥èããã¾ãåé¿ãã¦HTPãã³ãã©ã¼å ã®ãã¸ãã¯ãããªã¯ã¨ã¹ãããã«ã¦ã§ã¢ã«IDã渡ã2ã¤ã®ã¢ããã¼ããç´¹ä»ãã¾ãã
ã¢ããã¼ã1: http.ResponseWriter
å®è£
ãã©ãããã¦IDãä¼æ¬ãã
http.ResponseWriter
ã¤ã³ã¿ã¼ãã§ã¼ã¹ãæºãããªãã¸ã§ã¯ãã«IDãåã渡ãããAPIãä»è¾¼ãã®ãæåã®ã¢ããã¼ãã§ãã
ããã«ã¦ã§ã¢ã§http.ResponseWriter
ã¤ã³ã¿ã¼ãã§ã¼ã¹ãæºãããªãã¸ã§ã¯ãã¨ãã¦ã©ãããããªãã¸ã§ã¯ããä»è¾¼ãã§ãããã¨ã§ãå¾ç¶ã®å¦çããIDãã©ãããããªãã¸ã§ã¯ãã«ã»ããã§ãã¾ãã
ããã«ã¦ã§ã¢ã«ã¯ ãã¸ãã¹ãã¸ãã¯å®è¡å¾ã«http.ResponseWriter
ã¤ã³ã¿ã¼ãã§ã¼ã¹ãæºãããªãã¸ã§ã¯ãã確èªãããã¨ã§IDãåå¾å¯è½ã§ãã
å ·ä½çãªå®è£ ä¾ã¯æ¬¡ã®ããã«ãªãã¾ãã
https://go.dev/play/p/PuHWRSFBeeN
type ResponseWriterDecorator struct { http.ResponseWriter tenantID string } func (rwd *ResponseWriterDecorator) SetTenantID(tenantID string) { rdw.tenantID = tenantID } func (rwd *ResponseWriterDecorator) TenantID() string { return rdw.tenantID } func RequestLogMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rwd := &ResponseWriterDecorator{ResponseWriter: w} start := time.Now() next.ServeHTTP(rwd, r) // ãã³ãã©ã¼å ã§ã»ãããããuserIDãããã§åå¾ tenantID := rwd.TenantID() log.Printf("request log tenant_id=%q duration=%v\n", tenantID, time.Since(start)) }) } func businessLogicHandler(w http.ResponseWriter, r *http.Request) { // ãã¸ãã¹ãã¸ã㯠if rwd, ok := w.(*ResponseWriterDecorator); ok { rwd.SetTenantID("update from businessLogicHandler") } }
ãã®æ¹æ³ã®æ³¨æç¹
ãã®ã¢ããã¼ãã§ã¯ãããã«ã¦ã§ã¢ãrwd.TenantID
ã¡ã½ããçµç±ã§æ
å ±ãåãåºãã¾ãã
ãã¡ãªããã¨ãã¦ã¯HTTPãã³ãã©ã¼å
ã§http.ResponseWriter
ããã£ã¹ãã§ããã¨ããã¾ã§IDãæã£ã¦ããªãã¨ãããªãç¹ãããã¾ãã
ã«ã¼ããã¼ã ã®HTTPãã³ãã©ã¼ãHTTPã®å¦çããã層ã¨å
·ä½çãªãã¸ãã¹ãã¸ãã¯ãå®è¡ãã層ãåé¢ãã¦ãããããå
·ä½çãªã³ã¼ãã«ããã¨å°ãå¥å¦ã«ãªãã¾ãã
package handler import ( usecase ) func businessLogicHandler(w http.ResponseWriter, r *http.Request) { // ã¤ã³ããããHTTPã®ä¸çããGoã®æ§é ä½ã«å¤æããå¦ç input := parseInput(r.Body) // ãã¸ãã¹ãã¸ãã¯ãå®éã«å®è¡ããå¦ç result, tenantID, err := usecase.Execute(input) // ã¨ã©ã¼ã§ãããã³ãIDã¯ã»ãããã¦ããããã if rwd, ok := w.(*ResponseWriterDecorator); ok { if tenantID != "" { rwd.SetTenantID(tenantID) } } if err != nil { // ã¨ã©ã¼ã¬ã¹ãã³ã¹ãè¿ãå¦ç writeErrorResponse(w, err) return } // ãã¸ãã¹ãã¸ãã¯ã®çµæãHTTPã®ä¸çã«å¤æããå¦ç writeSuccessResponse(w, result) }
ã¾ããçæ³ã§ã¯ééçã§ããã¹ãããã«ã¦ã§ã¢ã®åå¨ãHTTPãã³ãã©ã¼ã®å é¨å®è£ ãèªç¥ãã¦ããå¿ è¦ãããã¾ãã
ããæ¹2: Context
ãªãã¸ã§ã¯ãã¸æ§é ä½ãªãã¸ã§ã¯ãã¸ã®ãã¤ã³ã¿ãè©°ãã
ããä¸ã¤ã®æ段ã¯ãContext
ãªãã¸ã§ã¯ãå
ã«æ§é ä½ãªãã¸ã§ã¯ãã®ãã¤ã³ã¿ãäºåã«WithValue
ã¡ã½ããã§è©°ãè¾¼ãã§ããããæ¹ã§ãã
åæç¥è1ã§è¿°ã¹ãéããå®éã®context
ãªãã¸ã§ã¯ãã®Value
ã«è©°ãããå¤ãèªä½ã¯ã¤ãã¥ã¼ã¿ãã«ã§ããããã®å¤ï¼ãã®å ´åãã¤ã³ã¿ã示ãå
ã«ãããªãã¸ã§ã¯ãï¼ã®ä¸ã®å¤ã¯å¤æ´å¯è½ã§ãã
å ·ä½çãªãµã³ãã«ã³ã¼ãã¯æ¬¡ã®ã¨ããã§ãã
https://go.dev/play/p/g3I27Unzy6Y
type requestInfoKey struct{} type RequestInfo struct { TenantID string } func RequestLogMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { info := &RequestInfo{} ctx := context.WithValue(r.Context(), requestInfoKey{}, info) start := time.Now() next.ServeHTTP(w, r.WithContext(ctx)) log.Printf("Access log tenant_id=%v duration=%v\n", info.TenantID, time.Since(start)) }) } func businessLogicHandler(w http.ResponseWriter, r *http.Request) { tenantID := "update from businessLogicHandler" if info, ok := r.Context().Value(requestInfoKey{}).(*RequestInfo); ok { info.TenantID = tenantID } }
ããã§ã¯ãRequestLogMiddleware
ã§*RequestInfo
ãã¤ã³ã¿ãContext
ãªãã¸ã§ã¯ãã«è©°ãã¦ãå¾ç¶ãã³ãã©ã¼ãRequestInfo.TenantID
ãã£ã¼ã«ãã¸å¤ãã»ãããã¾ãããªã¯ã¨ã¹ããã°åºåæã«ã¯info.TenantID
ã確èªãã¦ãã°ãåºåãã¾ãã
ãã®æ¹æ³ã®æ³¨æç¹
ãã®ææ³ã¯ãContext
ãªãã¸ã§ã¯ãã¸æ ¼ç´ãããã¼ã¿ãã¤ãã¥ã¼ã¿ãã«ã§ããã¹ãContext
ãªãã¸ã§ã¯ãã®Valueãæããªãã¸ã§ã¯ããæ´æ°ãã¦ãã¾ãã
ä½åãå¤ãæ¸ãæãããããªä½¿ãæ¹ãããã¨ã´ã«ã¼ãã³ã»ã¼ããªåæãå´©ãã¦ãã¾ãããããããã¯ã·ã§ã³ã³ã¼ãã§ã¯ããã«ã¦ã§ã¢ã¨å¥ã®å
±éã©ã¤ãã©ãªã®ä»çµã¿ã§ééçã«å®è¡ããããªã©ã®å·¥å¤«ãå¿
è¦ããã§ãã
ã¾ã¨ã
ä»åã¯ãªã¯ã¨ã¹ããã°ããã«ã¦ã§ã¢ã«å¾ç¶å¦çããä»å æ
å ±ã渡ãæ¹æ³ã2ã¤ç´¹ä»ãã¾ããã
ç§ã®ãã¼ã ã§ã¯ã¢ããã¼ã1ã®ãhttp.ResponseWriter
ãã©ããããããæ¡ç¨ãã¾ããã
https://go.dev/play/p/CyzOXqgHjXU
- ã¬ã¤ã¤ã¼éã®APIã¤ã³ã¿ã¼ãã§ã¼ã¹ãéåæãããå½¢ã«ãªã
- ããã«ã¦ã§ã¢ã§
http.ResponseWriter
ããã£ã¹ããã¦ãããã¨ããç¥ã£ã¦ãããã³ã¼ããã§ãã
ä¸ã®ãããªæ¸å¿µã¯ãããã®ã®ããªã¯ã¨ã¹ããã°ã«IDæ å ±ãã®ããã¨ã®æ¥ã ã®éç¨ã®ã«ã¤ã¼ã³å¹æãã¨ã¦ã大ãããããã³ã¼ãã®ãçæ³ããã®ãºã¬ãã¯è¨±å®¹ãã¾ããã
ãã£ã¨ã¹ãã¼ããªããããããåç¥ã®ããã¯æãã¦ããã ããã¨å¹¸ãã§ãã
ææ¥ã¯ã³ã¼ãã¬ã¼ãã¨ã³ã¸ãã¢ãªã³ã°å®¤ã® yuya-takeyama ããã®è¨äºã§ããã楽ãã¿ã«ï¼
-
å³å¯ã«ããã¨å¤é¨ã«å
¬éãã¦ããã¨ã³ããã¤ã³ã以å¤ã¯
GraphQL
ãããã¯Connect
ã§ããã¨ãããã¨ã³ããã¤ã³ãã§ãã↩