Курс «Архитектура мессенджера» · Часть 1 · Картина целиком

Раздел 2A. API-ручки (эндпоинты)

Что такое ручка-эндпоинт; как вывести API из требований; HTTP против WebSocket по направлению.

Это короткий, но важный практический раздел между «требованиями» (Раздел 2) и «соединением» (Раздел 3). Сняли требования — полезно набросать API-поверхность: список ручек, по которым клиент будет дёргать сервер. Это показывает интервьюеру, что ты умеешь переводить требования в конкретный контракт.

Что такое API-ручка

Ручка (endpoint) — это адрес, по которому клиент общается с сервером, и одно конкретное действие за ним. POST /messages — отправить сообщение, GET /messages/{chat_id} — загрузить историю.

Аналогия: меню в ресторане. Клиент (телефон) говорит серверу «хочу вот это» и называет конкретное блюдо — ручку. Меню — это список доступных действий.

Как понять, какие ручки нужны

Идёшь по функциональным требованиям и для каждого спрашиваешь — как клиент это запросит? Одно действие пользователя → одна ручка.

Нужны ли ручки, если есть WebSocket? Да — это разные оси

Главная ошибка — делить «фича → HTTP или WS». Делить надо по направлению:

  • Входящий запрос (клиент → сервер) — разовое действие. Обычно HTTP-ручка: загрузить историю, создать чат, добавить участника, выложить файл. Саму отправку сообщения тоже можно слать ручкой POST /messages (вернёт message_id/seq и закроет идемпотентность) — или кадром по уже открытому WebSocket. Оба варианта валидны; на интервью проговори, что выбор есть.
  • Исходящая доставка (сервер → клиент)всегда push по WebSocket. Ручкой нельзя дотолкнуть данные тому, кто прямо сейчас ничего не спрашивает. Сюда: новое сообщение получателю, «печатает…», онлайн-статус, галочки доставки/прочтения, входящий звонок.

Простое правило: запросил один раз → HTTP; прилетает само и постоянно → WebSocket. Открыл приложение → по HTTP подтянул список чатов и историю → поднял WebSocket → дальше всё новое идёт по нему.

Ручка почти всегда тянет за собой событие

Действие и оповещение — разные вещи:

  • PUT /messages/{id} (редактировать), DELETE /messages/{id} (удалить), POST /chats/{id}/members (добавить) — HTTP-ручка сохраняет изменение в БД…
  • …а потом сервер по WebSocket рассылает остальным участникам событие: «сообщение изменилось», «участник добавлен» — их клиенты обновляют экран.

То же и с отправкой: POST /messages (или WS-кадр) сохраняет и присваивает seq → сервер пушит сообщение получателям по их WebSocket. Ручки не работают в одиночку — почти каждая тянет за собой realtime-событие.

Контракт реального времени (события WebSocket)

У постоянного канала свой «список действий» — это события, а не URL: message.new, message.edited, message.deleted, receipt (доставлено/прочитано), typing, presence, call.invite/answer/ice. Это тоже часть API — назови её отдельно от REST-ручек.

Базовый набор HTTP-ручек

GET    /messages/{chat_id}?after={seq}&limit=50   история, пагинация по курсору
PUT    /messages/{msg_id}                         редактировать (+ WS-событие всем)
DELETE /messages/{msg_id}                         удалить (+ WS-событие всем)
POST   /messages                                  отправить (можно и по WS; вернёт
                                                  message_id, идемпотентность client_msg_id)
POST   /chats                                     создать чат
GET    /chats                                     список моих чатов
POST   /chats/{id}/members                        добавить участника
DELETE /chats/{id}/members/{user_id}              удалить участника
GET    /users/{id}/presence                       снимок статуса (живое — по WS)
POST   /media                                     pre-signed URL для загрузки
GET    /media/{file_id}                           скачать (обычно прямой CDN-URL)
POST   /auth/login · /auth/refresh · POST /devices · GET /users/me
POST   /keys · GET /users/{id}/prekeys            ключи для E2EE (Раздел 15)

Что часто забывают (а спрашивают)

  • Аутентификация и устройства. POST /auth/login, /auth/refresh, POST /devices (регистрация устройства и его push-токена APNs/FCM, Раздел 5), GET /users/me. Без этого нет ни сессии, ни мультидевайса (Раздел 10).
  • Ключи для E2EE. POST /keys (выложить prekeys), GET /users/{id}/prekeys (забрать чужие) — асинхронная установка ключа из X3DH (Раздел 15).
  • История — это пагинация по курсору, а не «отдай всё»: ?after={seq}&limit=50. Тот же курсор лежит в основе догона после офлайна (Разделы 6, 10).
  • Идемпотентность отправки. POST /messages несёт client_msg_id, чтобы ретраи по плохой сети не задвоили сообщение (Раздел 6).
  • Медиа грузится не через API. POST /media возвращает pre-signed URL, клиент льёт файл напрямую в объектное хранилище/CDN, минуя API-гейтвей; в сообщении едет только ссылка (Раздел 9).
  • REST-гигиена. Версия в пути (/v1/...), статус-коды (200/201/4xx), авторизация заголовком (Authorization: Bearer …), rate-limit, единый формат ошибок.