Проектирование под нефункциональные требования
Проектирование под нефункциональные требования
1. Что такое нефункциональные требования
Функциональные требования отвечают на вопрос «что система делает?» («Пользователь может оформить заказ»).
Нефункциональные — на вопрос «как хорошо она это делает?» («Заказ оформляется за ≤ 2 секунды при 10 000 одновременных пользователей»).
NFR часто формулируются расплывчато:
- «система должна быть надёжной»,
- «интерфейс должен быть быстрым»,
- «данные должны быть защищены».
Для проектирования такие формулировки бесполезны. Требуется количественная и измеримая спецификация:
| Категория | Плохая формулировка | Хорошая формулировка |
|---|---|---|
| Производительность | «Быстро» | «P95 latency для API /orders ≤ 300 мс при нагрузке 500 RPS» |
| Масштабируемость | «Масштабируется» | «Поддержка горизонтального масштабирования без downtime; добавление узла даёт ≥ 80% линейного роста throughput» |
| Отказоустойчивость | «Надёжная» | «Система сохраняет работоспособность при отказе одного узла в кластере; RTO ≤ 5 мин, RPO = 0» |
| Безопасность | «Защищена» | «Все внешние API требуют аутентификации по OAuth 2.0; чувствительные данные шифруются AES-256 at rest и TLS 1.3 in transit» |
| Сопровождаемость | «Легко поддерживать» | «Время локализации ошибки ≤ 15 мин по correlation ID; coverage unit-тестов ≥ 80%» |
Только такая формулировка позволяет сделать выбор: использовать кэширование или оптимизировать запрос? Внедрять репликацию или резервное копирование? Шифровать на уровне приложения или СУБД?
2. Масштабируемость
Масштабируемость — это способность системы сохранять характеристики при увеличении нагрузки. Она делится на два типа:
- Вертикальная (scale-up) — увеличение мощности одного узла (CPU, RAM, SSD).
- Горизонтальная (scale-out) — добавление новых узлов в кластер.
Проектирование под горизонтальную масштабируемость требует соблюдения нескольких принципов.
2.1. Отсутствие shared state
Если два экземпляра сервиса обращаются к одной общей переменной в памяти — масштабирование невозможно.
Решение:
- Вынос состояния во внешнее хранилище (Redis, PostgreSQL),
- Использование stateless-сервисов (все данные — в запросе или в БД),
- Шардирование сессий (sticky sessions — антипаттерн, но допустим при переходе).
2.2. Идемпотентность и безопасность операций
Как обсуждалось ранее, идемпотентность (PUT, DELETE) позволяет безопасно повторять запросы при сбоях сети — что неизбежно в распределённой среде.
Безопасные операции (GET) можно кэшировать на любом уровне (CDN, reverse proxy, gateway).
2.3. Асинхронность и декомпозиция
Снхронные цепочки вызовов (A → B → C) создают «точки затора». При росте нагрузки задержка умножается.
Решение:
- Замена синхронных вызовов на события (event-driven architecture),
- Вынос долгих операций в фон (job queues: RabbitMQ, Kafka, SQS),
- Использование CQRS: запись — синхронно, чтение — из материализованных витрин.
Пример: оформление заказа.
- Снхронный вариант: UI → OrderService → InventoryService → PaymentService → EmailService
- Асинхронный: UI → OrderService (создаёт заказ, публикует
OrderCreated) → фоновые обработчики
Первый — проще для отладки, но хрупок. Второй — сложнее, но масштабируется линейно.
2.4. Локальность данных (Данные locality)
Операции над данными должны происходить там, где они находятся.
- Если данные шардированы по
user_id, логика, работающая с пользователем, должна выполняться на том же узле. - В распределённых БД (CockroachDB, Yugabyte) это достигается через коллокацию (placement rules).
- В микросервисах — через владение данными (service owns its Данные).
Нарушение локальности → сетевые вызовы → рост latency и снижение throughput.
3. Отказоустойчивость
Отказоустойчивость — управление последствиями. Проектирование начинается с предположения: всё, что может сломаться — сломается.
3.1. Принципы устойчивости
| Принцип | Описание | Реализация в коде/архитектуре |
|---|---|---|
| Изоляция (Bulkheads) | Не дать сбою в одном компоненте повлиять на другие | Разделение пулов соединений, отдельные очереди для критических и некритических задач |
| Отказоустойчивость по умолчанию (Fail-safe) | При сбое — перейти в безопасное состояние | Возврат кэшированных данных, переключение на упрощённый режим («читалка работает, редактирование — нет») |
| Постепенное деградирование (Graceful degradation) | Сохранение частичной функциональности | Отключение аналитики при высокой нагрузке, отображение устаревших данных при недоступности БД |
| Самовосстановление (Self-healing) | Автоматическое восстановление без участия человека | Health-check + auto-restart, автоматическое переключение на реплику при таймауте |
3.2. Паттерны устойчивости
-
Circuit Breaker — «выключатель», который временно блокирует вызовы к нестабильному сервису после N сбоев. Позволяет избежать каскадного отказа.
Пример: библиотеки Polly (.NET), Resilience4j (Java), Hystrix (устаревает). -
Retry with Backoff — повтор с экспоненциальной задержкой. Важно: только для идемпотентных операций.
Правило: max 3 попытки, начальная задержка 100 мс, множитель 2. -
Timeout — явное ограничение времени ожидания. Без таймаута потоки «зависают», исчерпывая пул.
Рекомендация: таймаут вызова должен быть меньше, чем таймаут его клиента (правило 30-60-90: клиент 90 мс, сервис 60 мс, БД 30 мс). -
Fallback — альтернативный путь при сбое.
Пример: при недоступности рекомендательного движка — показать «популярные товары» из кэша.
3.3. Тестирование отказоустойчивости
Проектирование без проверки — теория. Необходимы:
- Chaos Engineering — намеренное введение сбоев (отключение узла, имитация latency) в staging/prod,
- Load + Failure Тестирование — нагрузка + одновременный сбой компонента,
- Game Days — симуляции инцидентов с участием команды.
Инструменты: Chaos Monkey, Gremlin, Toxiproxy.
4. Безопасность
Безопасность — встраивание контроля на каждом уровне.
4.1. Принципы безопасного проектирования
| Принцип | Описание | Пример в проектировании |
|---|---|---|
| Минимальные привилегии | Компонент имеет только те права, что необходимы | Микросервис OrderService имеет доступ только к таблице orders, не ко всей БД |
| Защита в глубину (Defense in depth) | Несколько независимых слоёв защиты | Валидация на gateway + на application layer + в домене + в БД (CHECK-ограничения) |
| Безопасность по умолчанию | Небезопасные настройки — запрещены | Все API закрыты по умолчанию; открытые — явно помечены [AllowAnonymous] |
| Аудит и отслеживаемость | Любое действие — логируется с контекстом | Запись в audit log: кто, что, когда, с каким correlation ID |
4.2. Контроль доступа: от RBAC к ABAC
-
RBAC (Role-Based Access Control) — права назначаются ролям (
admin,user). Прост, но не гибок.
Пример:adminможет удалять заказы → но что, если только свои? -
ABAC (Attribute-Based Access Control) — права зависят от атрибутов:
- Пользователя (
department == "finance"), - Ресурса (
order.ownerId == userId), - Контекста (
time < 18:00).
Пример на псевдокоде:
if (user.role === 'manager' && order.status === 'draft' && order.createdBy === user.id) {allow('delete');} - Пользователя (
ABAC сложнее, но соответствует реальным бизнес-правилам. Реализуется через политики (OPA — Open Policy Agent) или декларативные атрибуты.
4.3. Защита данных
- In transit — TLS 1.3, mutual TLS (mTLS) между сервисами.
- At rest — полное шифрование (TDE в СУБД) или на уровне приложения (шифрование полей
creditCardдо записи). - In use — защита памяти (secure enclaves, Intel SGX — редко, но для регуляторных систем).
Важно: ключи шифрования не хранятся в том же месте, что и данные. Используются KMS (Key Management Service): HashiCorp Vault, AWS KMS, Azure Key Vault.
5. Сопровождаемость
Система, которую невозможно понять, отладить или изменить — обречена. Сопровождаемость — это инвестиция в будущее.
5.1. Наблюдаемость (Observability)
Три столпа:
- Логи — структурированные (JSON), с
level,service,traceId,spanId. - Метрики — количественные показатели (latency, error rate, saturation).
- Трассировки — end-to-end цепочка вызовов через распределённую систему (OpenTelemetry).
Проектирование под наблюдаемость:
- Все входящие запросы получают
X-Request-ID, - Каждый лог содержит
correlationId, - Критические пути инструментированы
StartSpan()/EndSpan().
5.2. Тестируемость
Как обсуждалось ранее, модуль, который нельзя протестировать изолированно, — плохо спроектирован.
Признаки хорошей тестируемости:
- Зависимости инжектятся,
- Побочные эффекты вынесены (время, случайность, IO),
- Чистые функции выделены.
5.3. Документированность по замыслу
Документация — продукт проектирования:
- Контракты API (OpenAPI/Swagger) генерируются из кода,
- Диаграммы (C4) обновляются при изменении архитектуры,
- Decision Log (ADR — Architectural Decision Record) фиксирует почему было выбрано решение.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Каждая система имеет свою архитектуру построения; систему нужно разворачивать под нагрузку; нужно понимать обновления и исправление ошибок; рано или поздно — интеграция, безопасность, расширение и поддержка. Подход к проектированию — это стратегия, которая определяет, откуда начинается работа над системой и в каком порядке формируются её компоненты. Принципы — это критерии оценки. Они позволяют задать вопрос — Если бы мы сделали иначе, что пошло бы не так через год? Хороший код сегодня — это рабочий код и тот, который можно безопасно изменить… В современной практике термин сервис используется в нескольких значениях — Микросервис — автономное приложение со своей БД, жизненным циклом и API, Domain-сервис — класс в доменном слое, реализующий… Любое действие пользователя — это запрос на изменение состояния, а не прямая команда. Традиционный подход — Команда проектирует систему, Пишет код, По завершении — создаёт документацию для сдачи заказчику или архивирования Проектирование баз данных — это системная инженерная дисциплина, направленная на создание структуры хранения данных, которая обеспечивает корректность, целостность, производительность, расширяемость… Современные программные системы редко существуют изолированно. Переходите к изучению этой статьи только после того, как изучите микросервисы. Переходите к изучению этой статьи только после того, как изучите микросервисы. Распределённые системы представляют собой совокупность независимых вычислительных узлов, которые взаимодействуют между собой через сеть для достижения общей цели. Современные организации ежедневно генерируют огромные объёмы информации.Проектирование программных систем
Подходы к проектированию
Принципы проектирования
Проектирование сервисов и методов
Проектирование функциональных UI
Документация как инструмент проектирования
Проектирование баз данных
Проектирование API и интеграций
Паттерны микросервисной архитектуры
Проектирование веб-разработки
Проектирование распределенных систем
Хранилища DWH и ETL-процессы