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

Как построишь взаимодействие со сторонним API?

2.0 Middle🔥 121 комментариев
#Микросервисы и архитектура#Производительность и оптимизация

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

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

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

Архитектура взаимодействия с сторонним API в Go

При построении взаимодействия с сторонним API я ориентируюсь на прочность, тестируемость и гибкость системы. В Go это реализуется через комбинацию абстракций, контрактов и правильной организации слоев.

Стратегические принципы

  1. Изоляция логики взаимодействия: API клиент должен быть отделен от бизнес-логики. Это позволяет менять реализацию или тестировать без изменений в основной системе.
  2. Контрактное программирование: Определение четких интерфейсов (interface) для клиента и ответов. Это создает абстракцию над конкретной реализацией HTTP-клиента.
  3. Конфигурируемость: Все параметры взаимодействия (URL, таймауты, заголовки) должны быть конфигурируемыми и передаваться через структуры (struct), а не жестко закодированы.
  4. Обработка ошибок и повторные попытки: Механизм должен предусматривать четкую классификацию ошибок (сетевая, ошибка API, ошибка данных) и стратегии retry для повышения надежности.
  5. Метрики и мониторинг: Инструментирование клиента для отслеживания количества запросов, времени ответов и ошибок.

Практическая реализация

Я создаю несколько ключевых компонентов:

1. Определение интерфейса клиента и моделей данных

// Интерфейс абстрагирует конкретный HTTP клиент
type ExternalAPIClient interface {
    GetUser(ctx context.Context, id string) (*UserResponse, error)
    CreateOrder(ctx context.Context, order OrderRequest) (*OrderResponse, error)
}

// Модели для запроса и ответа (контракт с API)
type UserResponse struct {
    ID        string `json:"id"`
    Name      string `json:"name"`
    Email     string `json:"email"`
}

type OrderRequest struct {
    UserID    string  `json:"user_id"`
    Amount    float64 `json:"amount"`
}

2. Конфигурация клиента

type APIClientConfig struct {
    BaseURL    string
    Timeout    time.Duration
    RetryCount int
    APIKey     string
}

func NewAPIClientConfig(baseURL string, timeoutSec int) *APIClientConfig {
    return &APIClientConfig{
        BaseURL: baseURL,
        Timeout: time.Duration(timeoutSec) * time.Second,
        RetryCount: 3,
    }
}

3. Реализация клиента с использованием http.Client

type concreteAPIClient struct {
    config *APIClientConfig
    client *http.Client
    logger *zap.Logger // Для логирования
}

func NewConcreteAPIClient(config *APIClientConfig) ExternalAPIClient {
    return &concreteAPIClient{
        config: config,
        client: &http.Client{
            Timeout: config.Timeout,
        },
        logger: zap.NewExample(),
    }
}

func (c *concreteAPIClient) GetUser(ctx context.Context, id string) (*UserResponse, error) {
    url := fmt.Sprintf("%s/users/%s", c.config.BaseURL, id)
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("creating request: %w", err)
    }
    
    req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
    req.Header.Set("Content-Type", "application/json")

    // Реализация retry механизма
    var resp *http.Response
    for i := 0; i <= c.config.RetryCount; i++ {
        resp, err = c.client.Do(req)
        if err == nil && resp.StatusCode < 500 {
            break // Успешный ответ или клиентская ошибка
        }
        if err != nil || resp.StatusCode >= 500 {
            c.logger.Warn("API request failed", zap.Int("attempt", i+1), zap.Error(err))
            if i < c.config.RetryCount {
                time.Sleep(time.Duration(i+1) * 100 * time.Millisecond) // Progressive backoff
                continue
            }
        }
    }
    
    if err != nil {
        return nil, fmt.Errorf("http request failed after retries: %w", err)
    }
    
    defer resp.Body.Close()
    
    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("api error: status %d", resp.StatusCode)
    }
    
    var userResp UserResponse
    if err := json.NewDecoder(resp.Body).Decode(&userResp); err != nil {
        return nil, fmt.Errorf("decoding response: %w", err)
    }
    
    return &userResp, nil
}

4. Инструментирование и middleware

Для мониторинга я добавляю middleware-обертку, которая собирает метрики:

type instrumentedAPIClient struct {
    innerClient ExternalAPIClient
    metrics     *APIMetricsCollector
}

func NewInstrumentedAPIClient(inner ExternalAPIClient) ExternalAPIClient {
    return &instrumentedAPIClient{
        innerClient: inner,
        metrics:     NewAPIMetricsCollector(),
    }
}

func (c *instrumentedAPIClient) GetUser(ctx context.Context, id string) (*UserResponse, error) {
    start := time.Now()
    resp, err := c.innerClient.GetUser(ctx, id)
    duration := time.Since(start)
    
    c.metrics.RecordRequest("GetUser", duration, err == nil)
    
    return resp, err
}

Ключевые технологии и паттерны

  • context.Context: Используется для передачи таймаутов, трейсинга и управления жизненным циклом запроса.
  • Структурированное логирование: через zap или slog для диагностики проблем.
  • Метрики: интеграция с Prometheus или внутренней системой метрик для отслеживания SLA.
  • Тестирование: Использование моков (mock) интерфейса ExternalAPIClient в unit-тестах бизнес-логики и интеграционные тесты с реальным API или его симулятором (test server).
  • Резервные стратегии: При необходимости — кэширование ответов, fallback на другие API или данные.

Работа в production

В реальных проектах я также учитываю:

  • Circuit breaker (например, через библиотеку sony/gobreaker) для предотвращения лавины запросов к сломанному API.
  • Rate limiting со стороны клиента, если API имеет ограничения.
  • Пагинацию и потоковую обработку для больших данных.
  • Поддержку разных форматов авторизации (OAuth2, JWT, API ключи).
  • Парсинг и валидацию ответов с учетом возможных изменений в API контракте.

Такая архитектура позволяет создавать устойчивые и адаптируемые системы, которые могут работать даже при частичной деградации внешних сервисов, легко тестироваться и масштабироваться при изменении требований.