[Go]contextのイメージから使い方まで理解する
はじめに
func CreateHoge (ctx context.Context, userID uuid.UUID) error {}
開発をする中で上記のcontextというものがよく分からなかったので、調べるついでに記事にしようということで執筆しました。
想定する読者
-
contextの存在は知っているが、使い方に自信がない
-
「とりあえずctx渡しておけばいいんでしょ?」レベルから脱却したい
contextの概要とイメージ
まずはcontextの概要についてです。
こちらの記事でも載っていましたが、公式では下記のように説明しています。
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
(訳): contextパッケージで定義されているContext型は、処理の締め切り・キャンセル信号・API境界やプロセス間を横断する必要のあるリクエストスコープな値を伝達させることができます。
リクエストスコープ:ブラウザからのリクエスト(要求)がサーバーで処理され、レスポンス(応答)が返されるまでの間、特定のデータを保存できる範囲のこと
簡単にイメージするとリクエストスコープ内で情報を引き継ぐ箱のようなものです。
この箱に認証情報や、ログで出力したいIDなどを詰め込んで使っていきます。
contextにデータを詰め、取得してみる
それではcontextにデータを詰め込み、詰め込まれているデータの取得を具体的なコードで説明していきます。
まずは詰めるcontextを作るため、Backgroundで空のcontextを生成します。
ctx := context.Background()
次に作成したcontextに値を詰めるため、WithValueを使用します。
ctx = context.WithValue(ctx, "userID", "user-12345")
下記のように指定
-
第一引数:値を詰めるcontext
-
第二引数:キー
-
第三引数:詰める値
contextに詰めた値を取得するためにValueを使用します。
引数にはcontextに保存する際に指定したキーを渡すと、対象のキーの値を取得することができます。
id, ok := ctx.Value("userID").(string)
if !ok {
log.Println("userID not found in context")
return
}
fmt.Println(id) // "出力結果:user-12345"
実践的なコード例
実際に使う一例として、認証情報の伝播が挙げられます。
下記のコードでは以下のようなことを実現しています。
-
認証ミドルウェアであるAuthMiddlewareでcontextに認証情報(対象のユーザーが管理者か一般ユーザーかなどの情報)を詰める
-
各handler内部でcontextから引き出すような形にして、権限にあった処理の分岐を書ける形にしてあげる
type userContextKey struct{}
// 認証ミドルウェア
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
user, err := validateToken(token)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userContextKey{}, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ハンドラーで使用
func GetUserProfile(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(userContextKey{}).(*User)
// userの権限チェックなど
}
contextにはリクエストスコープで共通するデータのみ詰める
公式からは下記のように明記されていて、contextにはリクエストスコープで共通するデータを渡すようにして、関数の引数で渡すような値をcontextに詰めないようにとのことです。
Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
(訳)コンテキスト・バリューは、プロセスやAPIを通過するリクエスト・スコープのデータにのみ使用し、関数にオプションのパラメータを渡す際には使用しない。
具体例
// ページネーションのパラメータをcontextに入れることは非推奨
ctx = context.WithValue(ctx, "page", 1)
ctx = context.WithValue(ctx, "limit", 20)
// 関数の引数として渡すことを推奨
func GetUsers(ctx context.Context, page int, limit int) ([]User, error)
ページネーションのパラメータはGetUsersメソッドでのみ参照される情報なのでcontextに含めないよう推奨しているということですね。
終わりに
この記事ではcontextのイメージから基本的な使い方について解説しました。
私自身contextのイメージがつきづらく理解までに時間がかかったので、イメージつけの部分から記述しました。これを機に応用的なgoroutineでのcontextの使い方についても理解を深めていきたいと感じました。
お読みいただきありがとうございました。
Discussion