> Как сообщить клиенту о недоступности внешнего источника данных (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Клиенту нужно сообщить о недоступности внешнего источника данных через явный HTTP-статус (503 Service Unavailable) с телом ответа, содержащим machine-readable код ошибки и человекочитаемое сообщение. Важно не раскрывать внутренние детали инфраструктуры, но дать достаточно информации для диагностики. Используйте структурированный формат ошибки (например, RFC 7807 problem+json) и согласованный контракт ошибок во всём API. Таймауты и retry-логика должны быть настроены на стороне сервера, чтобы клиент получал ответ в разумное время, а не бесконечное ожидание.
Подробное объяснение
Недоступность внешнего источника данных - это временное или постоянное состояние, которое клиент должен отличать от других ошибок. Основные принципы:
- Статус-код: 503 Service Unavailable - семантически корректный выбор. Он сигнализирует, что сервер временно не может обработать запрос из-за перегрузки или недоступности зависимого сервиса. 500 Internal Server Error использовать не стоит - это скрывает причину и усложняет мониторинг.
- Заголовок Retry-After: обязателен для 503. Указывает клиенту, когда можно повторить запрос. Это снижает нагрузку на ваш сервис и помогает клиенту строить корректную retry-политику.
- Тело ответа: должно содержать:
code- стабильный строковый идентификатор ошибки (например,external_dependency_unavailable)message- краткое описание для разработчикаretry_after- дублирование заголовка в теле для клиентов, которые не читают заголовкиdetails- опционально, но без внутренних IP, hostname и стектрейсов
- Контракт: ошибка должна быть частью OpenAPI-спецификации. Клиент заранее знает, какие поля ожидать.
- Идемпотентность: если запрос мог быть частично обработан до падения внешнего источника, клиент должен иметь возможность безопасно повторить. Для этого используйте idempotency keys или явно документируйте поведение.
- Graceful degradation: если внешний источник нужен не для всех операций, рассмотрите fallback-механизмы (кэш, заглушки) и возвращайте 200 с пометкой
degraded: trueв теле. Но это отдельный архитектурный trade-off.
На практике
В Go-сервисе типичный сценарий:
- HTTP-хендлер вызывает клиент внешнего API через интерфейс с контекстом и таймаутом.
- При ошибке соединения, таймауте или 5xx от внешнего сервиса - логируем детали (для себя) и возвращаем 503 клиенту.
- Используем middleware для единообразного форматирования ошибок.
- Настраиваем мониторинг: метрика
external_dependency_failures_totalс labels по имени источника и типу ошибки. - Для критичных источников - circuit breaker (например,
sony/gobreaker), чтобы не долбить недоступный сервис.
Важно: не пробрасывайте ошибку внешнего сервиса как есть. Клиент не должен знать, что у вас внутри - это и безопасность, и стабильность контракта.
Пример кода
GOtype ErrorResponse struct {Code string `json:"code"`Message string `json:"message"`RetryAfter int `json:"retry_after,omitempty"`}func handleDependencyError(w http.ResponseWriter, r *http.Request, err error) {log.Printf("external dependency failed: %v", err)retryAfter := 5 // секундыw.Header().Set("Content-Type", "application/problem+json")w.Header().Set("Retry-After", strconv.Itoa(retryAfter))w.WriteHeader(http.StatusServiceUnavailable)json.NewEncoder(w).Encode(ErrorResponse{Code: "external_dependency_unavailable",Message: "внешний источник данных временно недоступен, повторите запрос позже",RetryAfter: retryAfter,})}// использование в хендлереfunc GetUserHandler(deps *Dependencies) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)defer cancel()user, err := deps.UserService.Fetch(ctx, userID)if err != nil {if errors.Is(err, context.DeadlineExceeded) || isDependencyError(err) {handleDependencyError(w, r, err)return}// другие ошибки - 500 или 4xxhandleGenericError(w, r, err)return}writeJSON(w, http.StatusOK, user)}}
Как отвечать на собеседовании
Начните с главного: 503 + Retry-After + структурированное тело. Затем объясните, почему это важно: клиент может корректно обработать ошибку, отличить от бага в вашем коде, построить retry-политику. Упомяните, что нельзя возвращать 500 - это маскирует проблему. Добавьте про контракт ошибок в OpenAPI и про то, что детали внешнего источника не должны утекать наружу. Если спросят про retry - скажите, что клиент должен использовать экспоненциальную задержку с джиттером, а сервер - ограничивать количество попыток. Хорошо упомянуть circuit breaker и graceful degradation как дополнительные меры.
Что проверяет интервьюер
- Понимание семантики HTTP-статусов и умение выбрать правильный.
- Умение проектировать контракт ошибок, а не просто возвращать текст.
- Осознание проблем мониторинга и наблюдаемости.
- Понимание взаимодействия между сервисами: таймауты, retry, backpressure.
- Уровень senior: способность видеть картину целиком - от клиента до внешнего источника, включая эксплуатационные аспекты.
Типичные ошибки
- Возврат 500 вместо 503 - клиент не может отличить вашу ошибку от недоступности зависимости.
- Отсутствие Retry-After - клиент не знает, когда повторять.
- Проброс внутренних деталей (hostname, IP, стектрейс) в теле ответа - утечка информации.
- Бесконечное ожидание ответа от внешнего источника без таймаута - клиент висит.
- Игнорирование идемпотентности - повторный запрос может создать дубликаты данных.
- Разные форматы ошибок в разных эндпоинтах - усложняет клиентскую обработку.
> Похожие задачи по backend
Зачем используются интерфейсы в Go
Передается ли JWT в зашифрованном или открытом виде
Как правильно работать с map и структурой в Go, чтобы избежать паники
Что такое кэширование и слои кэширования в контейнерах
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью