元ネタは@lestrrat さんの「Abusing type aliases to augment context.Context 」。
golangを用いてHTTPサーバを作る場合、ルーティングを定義するのに以下の様な関数を用います。
1 http.HandleFunc(path string, handler func(w http.ResponseWriter, r *http.Request) もちろん、http.Handleを用いる場合もありますし、gorilla/mux などのライブラリを用いることもあると思います。 ここで重要なのは、func(w http.ResponseWriter, r *http.Request)という引数の方です。
多くの場合、アプリケーションのハンドラ内ではデータベースなどの外部アプリケーション・ミドルウェアを用いることになります。 しかし、golangのHTTPアプリケーションでは、ハンドラ関数の形式がfunc (w http.ResponseWriter, r *http.Request)と決まっています。引数の追加はできないため、引数以外の方法でDB接続情報などを渡す必要があります。
これまで、golangでWebアプリケーション開発を行う場合によく用いられていたデータベースコネクションの保持方法は、dbパッケージを作成し、そこにパッケージ変数として持つ方法かと思います。が、グローバルな変数はできるだけ持ちたくない ですよね。
そこで、Go 1.8から追加されたcontext を使うことができます。http.Request にはcontext.Contextが入っていて、Request.Context() でget、Request.WithContext() でsetできます。
context.Contextに値を持たせる方法で最初に思いつくのはContext.WithValue() を用いる方法ですが、これは値を取得する度にtype assertionをする必要があり、あまりよくありません 。 これを解消するため、自分で型を定義するのがよいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package context // internal context subpackage import ( "context" "errors" ) type withSomethingContext struct { context.Context something *Something } func WithSomething(ctx context.Context, something *Something) context.Context { return &withSomethingContext{ Context: ctx, something: something, } } func Something(ctx context.Context) (*Something, error) { if sctx, ok := ctx.(*withSomethingContext); ok { if sctx.something != nil { return sctx.something, nil } } return nil, errors.New(`no asscosiated something`) } このように定義をすることで、毎回type assertionをする必要もなくなり、すっきりします。
...