Читать книгу 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 взрослым:

– ограничиваем частоту,

– защищаемся от повторов,

– пишем аудит,

– выбираем пагинацию,

– делаем наблюдаемость.

Теперь мы готовы к самой полезной инженерной привычке: сначала контракт, потом код.

Full stack Developer

Подняться наверх