> Какую архитектуру и технологии использовать для реализации клиента чата с REST API? (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Лига Цифровой Экономики

Стек: Go

> Пример ответа

Короткий ответ

Для Go-клиента чата с REST API оптимальна модульная архитектура: транспортный слой на net/http с context и таймаутами, слой доменных моделей, репозиторий для работы с API и сервисный слой для бизнес-логики. Технологии - стандартная библиотека net/http или resty для удобства, encoding/json для сериализации, context для отмены запросов. Для long polling или WebSocket - gorilla/websocket. Ключевое - разделение ответственности, обработка ошибок и идемпотентность.

Подробное объяснение

Архитектура клиента чата строится вокруг нескольких слоёв:

  • Транспортный слой - инкапсулирует HTTP-взаимодействие: базовый URL, заголовки, аутентификацию, ретраи. В Go это либо http.Client с настроенным Transport, либо обёртка над resty.
  • Слой моделей - структуры для запросов/ответов API: Message, User, ChatRoom. Они не должны зависеть от транспорта.
  • Репозиторий - интерфейс с методами SendMessage, GetMessages, CreateChat. Реализация использует транспорт и маппит JSON в модели.
  • Сервисный слой - оркестрация: валидация, кэширование, обработка пагинации, повторные попытки при сетевых ошибках.
  • Слой приложения - управление жизненным циклом: context для отмены, graceful shutdown, конфигурация.

Для чата критичны:

  • Таймауты - отдельно на соединение, на чтение ответа, на весь запрос.
  • Ретраи - с экспоненциальной задержкой и jitter, но только для идемпотентных методов (GET, PUT, DELETE).
  • Пагинация - курсорная или offset-based, реализуется в репозитории.
  • Long polling - если REST не поддерживает WebSocket, клиент должен уметь держать соединение с http.Client.Timeout больше обычного.

Технологии: net/http достаточно для большинства задач; resty добавляет удобные ретраи, middleware, JSON-хелперы. Для WebSocket - gorilla/websocket. Для конфигурации - envconfig или viper.

На практике

Начинаем с определения интерфейсов, чтобы тестировать репозиторий с моками. Транспорт настраивается один раз в main и передаётся в конструктор. Все методы принимают context.Context первым аргументом - это позволяет отменять запросы при shutdown или пользовательском действии.

Обработка ошибок: различаем транспортные ошибки (network, timeout) и бизнес-ошибки (4xx/5xx). Для последних - структурированный тип APIError с кодом и телом ответа. Ретраи только для 5xx и сетевых ошибок, не для 4xx.

Для long polling: отдельный метод PollMessages(ctx, chatID, cursor) с увеличенным таймаутом, который возвращает (messages, nextCursor, error). Приложение вызывает его в цикле с обработкой context.Canceled.

Пример кода

GO
type ChatClient struct {
repo *ChatRepository
}
type ChatRepository struct {
client *http.Client
baseURL string
token string
}
func (r *ChatRepository) SendMessage(ctx context.Context, msg *Message) error {
body, _ := json.Marshal(msg)
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, r.baseURL+"/messages", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+r.token)
req.Header.Set("Content-Type", "application/json")
resp, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("send message: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return &APIError{Code: resp.StatusCode, Message: "failed to send"}
}
return nil
}
func (r *ChatRepository) PollMessages(ctx context.Context, chatID string, cursor string) ([]Message, string, error) {
url := fmt.Sprintf("%s/chats/%s/messages?cursor=%s", r.baseURL, chatID, cursor)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := r.client.Do(req)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
var result struct {
Messages []Message `json:"messages"`
NextCursor string `json:"next_cursor"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, "", err
}
return result.Messages, result.NextCursor, nil
}

Как отвечать на собеседовании

Начни с верхнеуровневого разделения на слои, затем уточни, какие технологии выбираешь и почему. Подчеркни, что стандартная библиотека Go достаточна для 90% случаев, а сторонние библиотеки - только для удобства. Обязательно упомяни context и таймауты - это ключевой момент для клиентов. Расскажи про обработку ошибок и ретраи, покажи, что понимаешь разницу между идемпотентными и неидемпотентными запросами. Если спросят про long polling - объясни, как это влияет на настройку http.Client. Не углубляйся в детали реализации, пока не спросят.

Что проверяет интервьюер

  • Понимание принципов построения клиентов API: таймауты, ретраи, обработка ошибок.
  • Умение проектировать интерфейсы и разделять ответственность между слоями.
  • Знание Go-специфики: context, http.Client, json.Decoder.
  • Понимание trade-off между стандартной библиотекой и сторонними инструментами.
  • Способность объяснить, как клиент будет работать в реальном чате: пагинация, long polling, отмена запросов.

Типичные ошибки

  • Использование глобального http.DefaultClient без таймаутов - это приводит к зависаниям.
  • Игнорирование context - клиент нельзя отменить, что критично для чата.
  • Ретраи для POST без идемпотентности - можно дублировать сообщения.
  • Смешивание транспорта и бизнес-логики в одном слое - сложно тестировать.
  • Необработка io.EOF при long polling - сервер может закрыть соединение, нужно переподключаться.
  • Отсутствие лимитов на размер ответа - злоумышленник может прислать гигантский JSON.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью