Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что можно и нельзя добавлять в контекст в 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. Общие ресурсы с долгим сроком жизни
- Пул соединений к БД
- Конфигурация приложения
- Логгеры (лучше передавать явно)
- Кэши
- Бизнес/доменные объекты
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)
}
// Бизнес-логика здесь
}
Альтернативы для данных, которые не стоит помещать в контекст
- Для зависимостей — внедрение зависимостей через параметры функций
- Для конфигурации — отдельные структуры конфигурации
- Для логгеров — явная передача интерфейса логгера
- Для общих ресурсов — использование синглтонов или внедрение через структуры
Заключение
Контекст в Go следует использовать только для данных, непосредственно связанных с жизненным циклом конкретного запроса или операции. Это ключевой принцип, который обеспечивает предсказуемость и безопасность приложения. Передача зависимостей, конфигурации или общих ресурсов через контекст считается антипаттерном и приводит к проблемам с тестированием, поддержкой и пониманием кода. Всегда явно документируйте, какие значения ожидаются в контексте, и используйте типизированные ключи для предотвращения коллизий.