← Назад к вопросам
Как построишь взаимодействие со сторонним API?
2.0 Middle🔥 121 комментариев
#Микросервисы и архитектура#Производительность и оптимизация
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура взаимодействия с сторонним API в Go
При построении взаимодействия с сторонним API я ориентируюсь на прочность, тестируемость и гибкость системы. В Go это реализуется через комбинацию абстракций, контрактов и правильной организации слоев.
Стратегические принципы
- Изоляция логики взаимодействия: API клиент должен быть отделен от бизнес-логики. Это позволяет менять реализацию или тестировать без изменений в основной системе.
- Контрактное программирование: Определение четких интерфейсов (
interface) для клиента и ответов. Это создает абстракцию над конкретной реализацией HTTP-клиента. - Конфигурируемость: Все параметры взаимодействия (URL, таймауты, заголовки) должны быть конфигурируемыми и передаваться через структуры (
struct), а не жестко закодированы. - Обработка ошибок и повторные попытки: Механизм должен предусматривать четкую классификацию ошибок (сетевая, ошибка API, ошибка данных) и стратегии retry для повышения надежности.
- Метрики и мониторинг: Инструментирование клиента для отслеживания количества запросов, времени ответов и ошибок.
Практическая реализация
Я создаю несколько ключевых компонентов:
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 контракте.
Такая архитектура позволяет создавать устойчивые и адаптируемые системы, которые могут работать даже при частичной деградации внешних сервисов, легко тестироваться и масштабироваться при изменении требований.