Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что можно хранить в контексте?
В Go контекст (пакет context) — это специальный механизм для передачи метаданных, сигналов отмены и дедлайнов через границы API, особенно в асинхронных или распределённых системах. Важно понимать, что контекст не предназначен для хранения произвольных данных приложения. Его основная цель — управление жизненным циклом запросов и передача информации, необходимой для этого управления.
Рекомендуемые для хранения данные
Согласно официальной документации и best practices, в контексте следует хранить:
- Сигналы управления выполнением:
* **Дедлайны (Deadlines):** Момент времени, к которому операция должна завершиться. Устанавливаются через `context.WithDeadline`.
* **Таймауты (Timeouts):** Продолжительность времени, отведённая на операцию. Устанавливаются через `context.WithTimeout`.
* **Сигналы отмены (Cancellation Signals):** Механизм для явного запроса прекращения работы. Создаётся через `context.WithCancel`.
- Сквозная идентификация запросов (Request-scoped data): Данные, которые относятся к конкретному запросу или цепочке вызовов и должны передаваться через все слои приложения.
* **ID запроса (Request ID):** Уникальный идентификатор для трассировки и логирования.
* **ID трассировки (Trace ID) и ID спана (Span ID):** Для распределённой трассировки (например, в Jaeger или OpenTelemetry).
* **Метаданные аутентификации/авторизации:** Например, ID пользователя или роли, *но не сами токены или секреты*.
* **Информация для логирования:** Теги, которые должны добавляться ко всем логам в рамках данного запроса.
Как правильно хранить данные: использование ключей
Данные в контексте хранятся в виде пар ключ-значение, где ключ должен быть сравнимым (comparable), а значение — потокобезопасным для одновременного доступа из нескольких горутин. Ключи должны быть уникальными и избегать коллизий. Рекомендуемый подход — использовать неэкспортируемые типы.
package main
import (
"context"
"fmt"
)
// Объявляем неэкспортируемый тип для ключа, чтобы избежать коллизий.
type key string
// Константа с конкретным ключом.
const requestIDKey key = "requestID"
// Функция для добавления requestID в контекст.
func withRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
// Функция для извлечения requestID из контекста.
func getRequestID(ctx context.Context) (string, bool) {
id, ok := ctx.Value(requestIDKey).(string)
return id, ok
}
func main() {
ctx := context.Background()
ctx = withRequestID(ctx, "req-12345")
if id, ok := getRequestID(ctx); ok {
fmt.Printf("Request ID: %s\n", id) // Output: Request ID: req-12345
}
}
Что НЕЛЬЗЯ хранить в контексте
- Неприменимые к запросу данные: Глобальные переменные, конфигурацию приложения, подключения к базам данных или кэши. Для этого используйте внедрение зависимостей (dependency injection).
- Критичные для бизнес-логики данные: Основные параметры функций должны передаваться явно через аргументы. Контекст — это вспомогательный механизм.
- Секреты и чувствительная информация: Токены, пароли, ключи API. Контекст может логироваться или сериализоваться, что приведёт к утечке.
- Крупные объекты или структуры: Это может привести к неявному увеличению потребления памяти и усложнению отладки.
- Указатели на изменяемые данные: Это нарушает потокобезопасность. Если значение может измениться, его хранение в контексте небезопасно.
Практический пример использования
func processOrder(ctx context.Context, orderID int) error {
// 1. Проверяем, не отменён ли ещё контекст (например, пользователем).
if err := ctx.Err(); err != nil {
return fmt.Errorf("request cancelled before processing: %w", err)
}
// 2. Извлекаем сквозные данные (например, для логирования).
if reqID, ok := getRequestID(ctx); ok {
log.Printf("[%s] Processing order %d", reqID, orderID)
}
// 3. Передаём контекст вниз по цепочке вызовов (например, в HTTP-запрос или запрос к БД).
err := chargePayment(ctx, orderID)
if err != nil {
return err
}
// 4. Операция будет автоматически прервана, если сработает дедлайн контекста.
return updateInventory(ctx, orderID)
}
Ключевые выводы
- Контекст — это не хранилище общего назначения. Это интерфейс для передачи сигналов управления жизненным циклом и ограниченного набора метаданных запроса.
- Используйте контекст явно и осознанно. Он должен быть первым параметром в функции, если она выполняет операции ввода-вывода или блокирующие задачи.
- Данные в контексте должны быть иммутабельными и потокобезопасными.
- Всегда проверяйте
ctx.Err()в долгоиграющих операциях или циклах. - Избегайте создания собственных типов контекста, вместо этого используйте
context.WithValueс правильно объявленными ключами.
Следование этим принципам обеспечивает создание предсказуемого, отлаживаемого и корректно работающего в условиях конкурентности кода на Go.