Для чего в Go используется `context.WithValue` и какие есть правила его применения?

Ответ

context.WithValue используется для передачи данных, связанных с конкретным запросом (request-scoped data), вниз по стеку вызовов, не передавая их явно в качестве аргументов каждой функции.

Основная идея — пробросить сквозные метаданные, такие как ID запроса, данные аутентификации пользователя или информацию для трассировки.

Правила хорошего тона (Best Practices):

  1. Используйте только для сквозных данных. context предназначен для данных, которые могут понадобиться в любой точке обработки запроса, а не для передачи обязательных параметров функции.

    • Хорошо: context.WithValue(ctx, "request_id", "xyz-123")
    • Плохо: context.WithValue(ctx, "user_id", 123) (если user_id — это основной параметр, с которым работает функция, его нужно передавать явно).

  2. Используйте кастомные типы для ключей. Чтобы избежать конфликтов ключей между разными пакетами, никогда не используйте встроенные типы (например, string). Вместо этого объявляйте свой неэкспортируемый тип.



  3. Значения в контексте должны быть неизменяемыми (immutable), чтобы избежать гонок данных.


Пример правильного использования:

package main

import (
    "context"
    "fmt"
)

// 1. Объявляем кастомный тип для ключа (неэкспортируемый)
type key int

// 2. Объявляем переменную-ключ
const requestIDKey key = 0

func main() {
    // Создаем базовый контекст
    ctx := context.Background()

    // 3. Добавляем значение в контекст с помощью нашего ключа
    ctx = context.WithValue(ctx, requestIDKey, "abc-123-xyz")

    // Вызываем функцию, которая использует контекст
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    // 4. Извлекаем значение, безопасно проверяя тип
    if requestID, ok := ctx.Value(requestIDKey).(string); ok {
        fmt.Println("Processing request with ID:", requestID) // "Processing request with ID: abc-123-xyz"
    } else {
        fmt.Println("Request ID not found")
    }
}

Таким образом, context.WithValue — это мощный инструмент для передачи метаданных, но его следует использовать с осторожностью, чтобы не сделать код запутанным и неявным.