← Назад к вопросам

Что можно добавлять в контекст?

2.0 Middle🔥 201 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что можно и нельзя добавлять в контекст в Go

Контекст (context.Context) в Go — это специальный механизм для передачи данных, связанных с жизненным циклом запроса, отмены операций и таймаутов. Это НЕ общее хранилище данных, а инструмент для передачи именно контекстной информации, связанной с границами запроса или операции.

Что можно добавлять в контекст (рекомендуемые типы данных)

1. Значения, связанные с обработкой запроса

  • Идентификаторы запроса (request ID, trace ID) для сквозного логирования
  • Метаданные аутентификации/авторизации (ID пользователя, роли, токены)
  • Параметры для логирования (уровень детализации, флаги отладки)
  • Информация о источнике запроса (IP-адрес, User-Agent)

2. Флаги и настройки обработки

  • Таймауты и дедлайны (самое частое использование через context.WithTimeout)
  • Флаги отмены операций (через context.WithCancel)
  • Настройки поведения (например, флаг "только чтение" для БД)

3. Производные данные вычислений

  • Результаты промежуточных вычислений, если они нужны в нескольких слоях приложения
  • Соединения к внешним сервисам (только если они имеют ограниченный срок жизни запроса)

Что НЕЛЬЗЯ добавлять в контекст

1. Зависимости приложения (application dependencies)

// НЕПРАВИЛЬНО
type Database struct { /* ... */ }

func handler(ctx context.Context) {
    db := ctx.Value("database").(*Database) // АНТИПАТТЕРН
}

2. Общие ресурсы с долгим сроком жизни

  1. Пул соединений к БД
  2. Конфигурация приложения
  3. Логгеры (лучше передавать явно)
  4. Кэши
  5. Бизнес/доменные объекты

3. Изменяемые данные (mutable state)

// НЕПРАВИЛЬНО
type MutableData struct {
    counter int
}

func increment(ctx context.Context) {
    data := ctx.Value("counter").(*MutableData)
    data.counter++ // Опасность гонок данных!
}

Правила использования значений в контексте

1. Используйте типизированные ключи

// Правильный подход
type contextKey string

const (
    userIDKey contextKey = "userID"
    requestIDKey contextKey = "requestID"
)

// Установка значения
ctx := context.WithValue(parentCtx, userIDKey, "12345")

// Получение значения
if userID, ok := ctx.Value(userIDKey).(string); ok {
    // использование userID
}

2. Ограничивайте область видимости

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Добавляем только в контекст этого запроса
        ctx := context.WithValue(r.Context(), requestIDKey, generateID())
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

3. Документируйте ожидаемые значения

// В документации функции укажите, что ожидается в контексте
// ProcessOrder обрабатывает заказ. Ожидает в контексте:
// - userID (string) - идентификатор пользователя
// - deadline (time.Time) - дедлайн операции
func ProcessOrder(ctx context.Context, order Order) error {
    // ...
}

Практический пример правильного использования

package main

import (
    "context"
    "fmt"
    "time"
)

type contextKey string

const (
    traceIDKey contextKey = "traceID"
    userIDKey  contextKey = "userID"
)

func main() {
    // Базовый контекст с таймаутом
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    // Добавляем контекстные значения
    ctx = context.WithValue(ctx, traceIDKey, "trace-123")
    ctx = context.WithValue(ctx, userIDKey, "user-456")
    
    // Используем значения
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    // Проверяем таймаут
    select {
    case <-ctx.Done():
        fmt.Println("Контекст отменен:", ctx.Err())
        return
    default:
    }
    
    // Извлекаем значения с проверкой типа
    if traceID, ok := ctx.Value(traceIDKey).(string); ok {
        fmt.Println("Trace ID:", traceID)
    }
    
    if userID, ok := ctx.Value(userIDKey).(string); ok {
        fmt.Println("User ID:", userID)
    }
    
    // Бизнес-логика здесь
}

Альтернативы для данных, которые не стоит помещать в контекст

  1. Для зависимостей — внедрение зависимостей через параметры функций
  2. Для конфигурации — отдельные структуры конфигурации
  3. Для логгеров — явная передача интерфейса логгера
  4. Для общих ресурсов — использование синглтонов или внедрение через структуры

Заключение

Контекст в Go следует использовать только для данных, непосредственно связанных с жизненным циклом конкретного запроса или операции. Это ключевой принцип, который обеспечивает предсказуемость и безопасность приложения. Передача зависимостей, конфигурации или общих ресурсов через контекст считается антипаттерном и приводит к проблемам с тестированием, поддержкой и пониманием кода. Всегда явно документируйте, какие значения ожидаются в контексте, и используйте типизированные ключи для предотвращения коллизий.