Читать книгу Full stack Developer - Группа авторов - Страница 9
Раздел II. Продукт и требования, единые для всех реализаций.
Глава 7. Нефункциональные требования
ОглавлениеНефункциональные требования – это то, из-за чего продукт либо выглядит профессионально, либо выглядит как “оно работало у меня на ноутбуке”.
В TaskFlow мы добавим набор требований, которые встречаются почти везде:
– rate limiting,
– идемпотентность (как паттерн для POST и “платёжных” операций),
– audit log,
– пагинация (cursor vs offset),
– SLA и наблюдаемость (observability).
Это всё звучит серьёзно, но в реальности это просто набор договорённостей и инструментов.
7.1. Rate limiting (ограничение частоты запросов)
Зачем нужно
Если вы открываете регистрацию и логин в интернет, то к вам придут:
– боты,
– перебор паролей,
– странные сканеры,
– и пользователи, которые “обновляют страницу каждые 200 мс”, потому что им тревожно.
Rate limiting решает две задачи:
1) Защищает инфраструктуру (CPU/DB/сеть).
2) Защищает пользователей (брутфорс на логин/пароль).
Что именно лимитируем
Минимум для TaskFlow:
– /auth/register – лимит на IP и/или на email
– /auth/login – лимит на IP и на аккаунт (email)
– “тяжёлые” списки (поиск задач) – лимит на пользователя
Как выражаем лимит
Удобно мыслить в терминах:
– N запросов за T секунд (например, 10 запросов/мин)
– отдельные лимиты для разных endpoint’ов
Пример политики (условно):
– регистрация: 5/час на IP
– логин: 10/10 минут на IP + 5/10 минут на email
– поиск задач: 60/мин на пользователя
Что возвращаем клиенту
Если лимит превышен, стандартно:
– HTTP 429 Too Many Requests
– заголовок Retry-After (сколько ждать)
И важный момент: сообщение в ответе не должно быть токсичным.
“Too many requests, retry later” – нормально. “Вы слишком много хотите” – это уже лишнее.
7.2. Idempotency для POST (и “платёжных” операций как паттерн)
Идемпотентность – это способность повторить запрос и получить тот же результат, не создав дубликатов.
Почему это важно:
– сеть ненадёжна,
– клиент может не получить ответ и повторить запрос,
– прокси может повторить запрос,
– пользователь может нажать “создать” дважды (классика жанра).
Где нужна идемпотентность
В TaskFlow критично для:
– POST /tasks (создание задач)
– POST /projects
– любые “операции эффекта” (например, отправка webhook/уведомления, загрузка файла – по дизайну)
Идея из платежей:
– клиент отправляет Idempotency-Key
– сервер по этому ключу понимает: это повтор или новый запрос
– повтор возвращает прежний результат
Как это выглядит
Клиент отправляет заголовок:
– Idempotency-Key: <random-uuid>
Сервер:
1) проверяет, есть ли запись по ключу + пользователю + endpoint’у
2) если есть – возвращает сохранённый результат (например, 201 и тело созданной задачи)
3) если нет – выполняет операцию, сохраняет результат и возвращает
Важные детали
– Ключ должен быть уникальным на сторону клиента (обычно UUID).
– Хранить идемпотентные ключи надо с TTL (например, 24 часа).
– Нужно привязывать ключ к пользователю (или workspace), иначе один пользователь сможет “повторить” чужую операцию.
Что сохранять
Минимально:
– статус-код,
– response body,
– время создания,
– fingerprint запроса (опционально).
И ещё правило: если по тому же ключу пришёл другой запрос (другая payload), можно вернуть 409 Conflict. Это честно и защищает от странных багов клиента.
7.3. Audit log (журнал аудита)
Аудит – это ответ на вопрос: кто и что сделал.
Даже если вы не банк, аудит полезен:
– для разборов инцидентов,
– для поддержки пользователей,
– для безопасности (“кто удалил проект?”),
– для аналитики.
Что писать в аудит
События минимум:
– user зарегистрировался
– user вошёл (логин)
– создан/обновлён/архивирован проект
– создана/обновлена/удалена задача
– добавлен/изменён/удалён комментарий
– изменения ролей и участников workspace
Структура записи (концептуально)
AuditEvent:
– id
– workspace_id (если событие в workspace)
– actor_user_id (кто сделал)
– action (например: task.created, project.archived)
– entity_type и entity_id
– timestamp
– metadata (JSON: старые/новые значения, ip, user-agent и т.п.)
Где хранить
Для учебного проекта:
– отдельная таблица в основной БД – нормально.
В больших системах:
– иногда события идут в отдельное хранилище/лог-систему.
Тонкий момент: PII
Если у вас есть персональные данные, не пишите в аудит лишнее.
Например, пароль и токены – никогда.
Email – можно, но лучше как идентификатор, не как “лог всего”.
7.4. Pagination: cursor vs offset (сравнение и выбор)
Списки задач, комментариев, проектов – это обязательно пагинация.
Есть два основных подхода:
– Offset pagination: limit=20&offset=40
– Cursor pagination: limit=20&cursor=…
Offset pagination
Плюсы:
– простая для понимания,
– легко прыгать на “страницу 5”.
Минусы:
– на больших данных может быть медленнее (offset заставляет БД “пропускать” строки),
– нестабильность при изменениях: если между запросами добавили записи, страница “поплывёт”.
Cursor pagination
Плюсы:
– стабильнее при добавлениях/удалениях,
– обычно лучше по производительности на больших объёмах,
– идеально подходит для “бесконечной ленты”.
Минусы:
– сложнее для клиентов,
– “страница 5” как концепция исчезает (есть только “вперёд/назад”, если реализовано).
Что выбираем для TaskFlow
Реалистичный выбор:
– для задач и комментариев: cursor pagination
– для простых справочников (labels, members) можно offset, но лучше быть последовательными
Cursor обычно строится на:
– (created_at, id) как “ключ сортировки”
– курсор кодируется в base64 и передаётся как строка
Пример сортировки:
– “новые сверху”: сортируем по created_at DESC, id DESC
– курсор хранит последнюю запись текущей страницы
7.5. SLA и Observability (наблюдаемость)
Что такое SLA в нашем контексте
SLA обычно формализуют в процентах доступности (“99.9%”). Для учебного проекта важнее другая мысль:
> Мы должны уметь доказать, что сервис жив, и быстро понять, если он болен.
Минимальные SLO (цели) для API:
– p95 latency для основных endpoint’ов, например < 300–500 мс (условно)
– error rate < 1%
– доступность (uptime) по health-check
Что нужно из observability
Минимальный набор, который стоит закладывать:
– структурированные логи (JSON, с request_id)
– метрики (количество запросов, latency, коды ответов)
– трассировка (trace_id) – опционально, но полезно
Обязательные практики:
– request_id генерируется на входе и прокидывается дальше
– логируем: метод, путь, статус, время обработки, user_id (если есть), workspace_id (если есть)
Health checks
Обычно:
– /health – быстрый, без БД (жив ли процесс)
– /ready – проверка зависимостей (БД доступна, миграции применены)
Нюанс: readiness не должен “ддосить” базу. Проверки должны быть лёгкими.
7.6. Мини-итог главы
Мы договорились о вещах, которые делают API взрослым:
– ограничиваем частоту,
– защищаемся от повторов,
– пишем аудит,
– выбираем пагинацию,
– делаем наблюдаемость.
Теперь мы готовы к самой полезной инженерной привычке: сначала контракт, потом код.