Содержание

Интеграции с внешними API в Go

Внешний API - это код, которым вы не управляете. Он может быть медленным, временно недоступным, изменить формат ответа, вернуть неожиданный статус, ограничить количество запросов или прислать ошибку в нестандартном JSON.

Поэтому хорошая интеграция не просто "делает HTTP-запрос". Она изолирует риски: хранит ключи безопасно, ставит таймауты, проверяет статус-коды, обрабатывает rate limits, логирует без секретов и отделяет внешний формат от внутренней бизнес-логики.

Конфигурация и API-ключи

API-ключи храните в переменных окружения:

apiKey := os.Getenv("NEWS_API_KEY") if apiKey == "" { return errors.New("NEWS_API_KEY is required") }

Не записывайте ключи прямо в код:

const apiKey = "real-secret-key" // плохо

Такой ключ легко случайно закоммитить. Если секрет попал в Git, его нужно ротировать, а не только удалить из текущего файла.

Не вставляйте ключи в логи. Если нужно залогировать URL, удалите секретные query parameters:

slog.Info("request external api", "url", redactedURL)

HTTP-клиент

У HTTP-клиента должен быть timeout:

client := &http.Client{ Timeout: 10 * time.Second, }

Каждый запрос должен иметь:

  • timeout;
  • обработку сетевой ошибки;
  • проверку статус-кода;
  • лимит размера ответа;
  • понятную ошибку;
  • retry только там, где это безопасно.

Пример:

req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("build request: %w", err) } req.Header.Set("X-Api-Key", apiKey) resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status: %s", resp.Status) }

Авторизация

Внешние API используют разные схемы:

  • API key в заголовке;
  • Bearer token;
  • OAuth2;
  • query parameter с ключом;
  • signed request.

Пример Bearer:

req.Header.Set("Authorization", "Bearer "+token)

Пример API key:

req.Header.Set("X-Api-Key", apiKey)

Если документация API предлагает передавать ключ в query parameter, будьте осторожны с логами: URL часто сохраняются целиком.

Rate limits

Rate limit означает, что API разрешает только ограниченное количество запросов за период. При превышении часто возвращается:

429 Too Many Requests

Иногда в заголовках есть подсказки:

Retry-After: 30 X-RateLimit-Remaining: 0

Хорошая интеграция не долбит API бесконечно. Она уважает лимиты, использует кэш, backoff и понятную ошибку пользователю.

Retry

Повторные попытки нужны только для временных ошибок:

  • timeout;
  • 502 Bad Gateway;
  • 503 Service Unavailable;
  • 504 Gateway Timeout;
  • некоторые сетевые сбои.

Не стоит retry-ить:

  • 400 Bad Request;
  • 401 Unauthorized;
  • 403 Forbidden;
  • валидационные ошибки;
  • операции, которые могут создать дубликат без idempotency key.

Retry должен иметь лимит и backoff:

delay := 200 * time.Millisecond for attempt := 1; attempt <= 3; attempt++ { err := callExternalAPI(ctx) if err == nil { return nil } select { case <-ctx.Done(): return ctx.Err() case <-time.After(delay): delay *= 2 } }

Изолируйте интеграцию

Не размазывайте детали внешнего API по всему проекту. Сделайте отдельный клиент:

type WeatherClient interface { CurrentWeather(ctx context.Context, city string) (Weather, error) }

Сервис работает с интерфейсом, а конкретная реализация знает URL, headers и формат ответа.

Так бизнес-логику проще тестировать: в тесте можно подставить тестовый клиент.

Telegram Bot API

Для Telegram-бота важны:

  • токен бота из переменной окружения;
  • команды /start, /help;
  • обработка текстовых сообщений;
  • экранирование MarkdownV2, если используете форматирование;
  • graceful shutdown;
  • отделение Telegram-слоя от бизнес-логики.

Команда /start должна дать пользователю понятный первый шаг. Ошибки отправки сообщения нужно логировать, но не печатать token.

Open-Meteo и погодные API

Погодные API часто требуют два шага:

  1. Геокодинг города в координаты.
  2. Прогноз по координатам.

Кэшируйте геокодинг: координаты города редко меняются. Также нормализуйте название города, потому что Almaty, almaty и Алматы должны вести себя предсказуемо.

NewsAPI и контентные API

Для новостей важно:

  • нормализовать дату;
  • дедуплицировать статьи;
  • хранить источник;
  • не доверять заголовкам как уникальному ID;
  • обрабатывать rate limits;
  • учитывать, что контент может быть пустым или обрезанным.

Контентные API часто возвращают данные разного качества. Код должен быть готов к пустым полям.

Gemini и AI API

Для AI API добавьте:

  • таймаут;
  • ограничение размера prompt;
  • логирование модели без API-ключа;
  • retry только при временных ошибках;
  • fallback или понятную ошибку пользователю;
  • учет стоимости и лимитов, если API платный.

OAuth2

OAuth2 нужен, когда пользователь дает доступ к своему аккаунту: Google Drive, Yandex Disk и другие сервисы.

Обычно flow такой:

  1. Перенаправить пользователя на страницу провайдера.
  2. Получить code на callback endpoint.
  3. Обменять code на access token.
  4. Сохранить refresh token безопасно.
  5. Обновлять access token по необходимости.

OAuth2 сложнее API key, потому что token принадлежит конкретному пользователю. Его нужно хранить безопасно и уметь обновлять.

Чеклист

  1. Ключи приходят из env.
  2. HTTP-клиент имеет timeout.
  3. Все запросы принимают context.Context.
  4. Статус-коды проверяются явно.
  5. Rate limits обрабатываются.
  6. Retry ограничены и применяются только к временным ошибкам.
  7. Секреты не пишутся в логи.
  8. Интеграция отделена интерфейсом от бизнес-логики.
  9. Внешний JSON преобразуется во внутренние типы.
  10. Для OAuth2 refresh token хранится безопасно.

Следующий шаг после статьи

Закрепите тему в реальном проекте и продолжайте обучение на курсах Praxis.

Продолжить изучение

Выбери следующую статью по маршруту или углубись в смежную тему.

Похожие статьи