Пошук уроків, статей та іншого контенту
Навчитеся розрізняти функціональні та нефункціональні вимоги й визначати їхній вплив на архітектуру.
Вимога — це опис того, що система повинна робити або якими властивостями повинна володіти.
Вимоги потрібні, щоб:
зрозуміти очікування користувачів і бізнесу;
визначити межі системи;
обрати архітектуру;
перевірити, чи готова система до використання;
оцінити компроміси між вартістю, швидкістю розробки та якістю.
У системному дизайні вимоги зазвичай поділяють на:
Функціональні — що система робить.
Нефункціональні — як саме система це робить і які обмеження має виконувати.
Функціональна вимога описує поведінку або можливість системи.
Вона відповідає на запитання:
Яку операцію повинна підтримувати система?
Приклади:
користувач може створити обліковий запис;
користувач може увійти за електронною поштою та паролем;
клієнт може створити замовлення;
продавець може змінити статус замовлення;
система надсилає електронний лист після успішної оплати;
Функціональні вимоги часто описують через дії користувача або іншої системи:
Якщо клієнт надсилає коректні дані замовлення, система створює замовлення та повертає його ідентифікатор.
Для сервісу замовлень:
Клієнт може створити замовлення з одним або кількома товарами.
Система перевіряє, що кожен товар існує.
Система зберігає замовлення зі статусом created.
Клієнт може отримати замовлення за його ідентифікатором.
Клієнт може скасувати замовлення, якщо воно ще не передане в доставку.
Ці вимоги описують функції, але майже нічого не кажуть про швидкість, доступність або спосіб реалізації.
Нефункціональна вимога описує властивість системи, обмеження або критерій якості.
Вона відповідає на запитання:
Наскільки добре система повинна виконувати свої функції?
Приклади:
95% запитів повинні оброблятися не довше ніж за 300 мс;
система повинна бути доступною 99,9% часу на місяць;
система повинна обробляти 1000 запитів на секунду;
дані повинні передаватися через захищене з'єднання;
після перезапуску сервісу замовлення не повинні втрачатися;
система повинна зберігати історію замовлень протягом 5 років.
Нефункціональні вимоги часто називають атрибутами якості. Найпоширеніші з них:
продуктивність;
масштабованість;
доступність;
надійність;
безпека;
спостережуваність;
зручність використання;
підтримуваність;
відповідність законодавчим або бізнес-обмеженням.
Розглянемо один сценарій.
Користувач може переглянути список своїх замовлень.
Список замовлень завантажується не довше ніж за 500 мс для 95% запитів.
Система підтримує 10 000 одночасних користувачів.
Перша вимога визначає функцію. Інші визначають якість її виконання та обмеження.
Одна функціональна вимога може мати багато нефункціональних вимог:
працює швидко;
не втрачає дані;
доступна під час відмови одного сервера;
не розкриває дані іншого користувача;
витримує потрібне навантаження.
Функціональні вимоги визначають основні частини системи:
які API потрібні;
які сутності та дані зберігати;
які операції підтримувати;
які сервіси або модулі створити;
які бізнес-правила реалізувати.
Нефункціональні вимоги визначають, як ці частини повинні працювати:
яку базу даних обрати;
чи потрібне кешування;
чи потрібні черги повідомлень;
як масштабувати сервіс;
як організувати резервування;
які метрики та журнали збирати;
як захищати дані.
Припустімо, що є функціональна вимога:
Користувач може отримати поточний баланс рахунку.
Можлива проста реалізація:
один застосунок;
одна база даних;
один запит до таблиці рахунків.
Тепер додамо нефункціональні вимоги:
95% відповідей — швидше за 100 мс;
5000 запитів на секунду;
доступність 99,99%;
баланс не можна показувати зі застарілими даними.
Архітектура може потребувати:
індексу для швидкого пошуку рахунку;
кількох екземплярів застосунку;
балансувальника навантаження;
реплікації бази даних;
уважного вибору між кешуванням і актуальністю даних;
моніторингу затримки та помилок.
Функція залишилася тією самою, але нефункціональні вимоги суттєво змінили рішення.
Функціональна вимога повинна бути:
конкретною;
однозначною;
перевірюваною;
пов'язаною з користувачем або зовнішньою системою.
Невдалий варіант:
Система повинна підтримувати замовлення.
Незрозуміло:
хто створює замовлення;
які дані потрібні;
що відбувається після створення;
які помилки можливі.
Кращий варіант:
Авторизований клієнт може створити замовлення, передавши ідентифікатори товарів і їхню кількість. Якщо всі товари доступні, система зберігає замовлення зі статусом
createdі повертає його ідентифікатор.
Нефункціональну вимогу бажано формулювати з такими складовими:
Метрика — що вимірюємо.
Цільове значення — якого результату очікуємо.
Умова — за яких обставин.
Частка або межа — для якої кількості запитів чи подій.
Спосіб перевірки — як переконатися, що вимога виконана.
Невдалий варіант:
Система повинна бути швидкою.
Кращий варіант:
За навантаження до 1000 запитів на секунду 95% запитів до
GET /orders/{id}повинні завершуватися не довше ніж за 300 мс.
Невдалий варіант:
Система повинна бути надійною.
Кращий варіант:
Після відмови одного екземпляра сервісу нові запити повинні оброблятися іншим екземпляром не пізніше ніж через 30 секунд, а підтверджені замовлення не повинні втрачатися.
Нижче наведено спрощену реалізацію функції створення замовлення та перевірки двох вимог:
функціональної: коректне замовлення отримує ідентифікатор;
нефункціональної: операція завершується в межах заданого часу.
const { performance } = require("node:perf_hooks");
const products = new Map([
["book", { name: "Книга", price: 300 }],
["pen", { name: "Ручка", price: 50 }]
]);
const orders = new Map();
function createOrder(items) {
if (!Array.isArray(items) || items.length === 0) {
throw new Error("Замовлення повинно містити хоча б один товар");
}
let total = 0;
for (const item of items) {
const product = products.get(item.productId);
if (!product) {
throw new Error(`Товар не знайдено: ${item.productId}`);
}
if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
throw new Error("Кількість товару повинна бути додатним цілим числом");
}
total += product.price * item.quantity;
}
const order = {
id: `order-${orders.size + 1}`,
items,
total,
status: "created"
};
orders.set(order.id, order);
return order;
}
// Перевірка функціональної вимоги
const order = createOrder([
{ productId: "book", quantity: 2 },
{ productId: "pen", quantity: 1 }
]);
if (order.status !== "created") {
throw new Error("Функціональна вимога не виконана");
}
if (order.total !== 650) {
throw new Error("Неправильно обчислено загальну суму");
}
console.log("Замовлення створено:", order);
// Перевірка простої нефункціональної вимоги щодо затримки
const startedAt = performance.now();
createOrder([
{ productId: "book", quantity: 1 }
]);
const elapsedMs = performance.now() - startedAt;
const maximumAllowedMs = 50;
if (elapsedMs > maximumAllowedMs) {
throw new Error(
`Занадто повільна операція: ${elapsedMs.toFixed(2)} мс`
);
}
console.log(`Час виконання: ${elapsedMs.toFixed(2)} мс`);У реальній системі перевірка продуктивності повинна виконуватися під контрольованим навантаженням і для великої кількості запитів. Один швидкий виклик не доводить, що система відповідає вимозі щодо продуктивності.
Вимоги можуть конфліктувати між собою.
Наприклад:
сильніша перевірка даних може збільшити час відповіді;
синхронний запис у кілька сховищ може підвищити надійність, але зменшити продуктивність;
кешування може зменшити навантаження, але повертати застарілі дані;
шифрування та додаткові перевірки можуть збільшити використання ресурсів;
дуже тривале зберігання даних збільшує витрати на сховище.
Тому під час проєктування потрібно не лише перелічити вимоги, а й визначити:
які з них обов'язкові;
які мають найвищий пріоритет;
які компроміси допустимі;
як вимірювати результат;
що відбувається, якщо виконати вимогу неможливо.
Перед вибором компонентів архітектури поставте такі запитання:
Хто виконує дію?
Які дані надходять на вхід?
Який результат очікується?
Що відбувається при некоректних даних?
Які стани або переходи має підтримувати система?
Чи потрібна операція одразу, чи її можна виконати асинхронно?
Яка максимальна або середня кількість запитів?
Яка прийнятна затримка?
Яка потрібна доступність?
Чи можна повертати застарілі дані?
Які дані потрібно захищати?
Що станеться під час відмови компонента?
Як перевірити, що вимога виконана?
Запишіть функціональні можливості системи.
Для кожної важливої можливості визначте нефункціональні вимоги.
Замініть нечіткі слова на вимірювані показники.
Визначте пріоритети.
Оцініть вплив вимог на архітектуру.
Перевірте вимоги за допомогою тестів, навантаження, моніторингу або перевірки конфігурації.
Наприклад:
Користувач може отримати історію платежів.
Це функціональна вимога. Її можна доповнити так:
історія містить платежі за останні 5 років;
перша сторінка містить не більше 50 записів;
відповідь для 95% запитів надходить за 400 мс;
користувач бачить лише власні платежі.
Останні вимоги вже впливають на схему даних, індекси, пагінацію, авторизацію та моніторинг.
Система повинна швидко надсилати повідомлення.
Тут поєднано дві частини:
функціональна: система надсилає повідомлення;
нефункціональна: повідомлення надсилається швидко.
Краще записати їх окремо та вказати конкретну межу часу.
Слова швидкий, надійний, безпечний, великий і зручний без метрик складно перевірити.
Замість них використовуйте:
мілісекунди для затримки;
запити за секунду для пропускної здатності;
відсоток доступності;
кількість одночасних користувачів;
час відновлення після відмови;
конкретні правила доступу.
Якщо спочатку реалізувати лише функції, а потім додати продуктивність або доступність, може виявитися, що базова архітектура не підтримує потрібний масштаб.
Нефункціональні вимоги потрібно враховувати до вибору сховища, меж сервісів і способу обробки запитів.
Усі вимоги не можуть бути однаково важливими. Наприклад, для платіжної системи безпека та коректність даних можуть мати вищий пріоритет, ніж мінімальна затримка.
Для кожної вимоги варто визначити:
обов'язкова вона чи бажана;
який ризик виникає при її невиконанні;
скільки коштує її реалізація;
як її перевірити.
Фраза «система має підтримувати багато користувачів» не визначає конкретного навантаження.
Потрібно уточнити:
скільки користувачів загалом;
скільки одночасно активних;
скільки запитів надходить за секунду;
який час відповіді прийнятний;
чи є пікові періоди.
Функціональні вимоги описують, що система повинна робити.
Нефункціональні вимоги описують якість роботи системи та її обмеження.
Функціональні вимоги визначають можливості, API, дані та бізнес-правила.
Нефункціональні вимоги впливають на масштабування, зберігання, доступність, безпеку та спостережуваність.
Нечіткі вимоги потрібно перетворювати на вимірювані критерії.
Вимоги можуть конфліктувати, тому важливо визначати їхні пріоритети та компроміси.
Архітектуру слід проєктувати з урахуванням обох типів вимог, а не додавати нефункціональні властивості в кінці.