Досвід архітектурного редизайну та розмежування бекенду й фронтенду
Будь-який вебпроєкт у своєму життєвому циклі проходить певну еволюцію: зазвичай все починається з простого, монолітного рішення на якомусь популярному фреймворку, швидкого розгортання та перших задоволених користувачів. Проте настає момент, коли архітектура починає тріщати по швах під тиском нових бізнес-вимог, маркетингових ініціатив і зростаючого органічного трафіку. Ми в нашій команді зіткнулися з цим класичним болем, коли один із ключових клієнтських сервісів раптово виріс утричі за кілька місяців. Здавалося б, це класична історія успіху та омріяне масштабування, про яке мріє кожен фаундер. Але на практиці виявилося, що наш старий підхід до розробки та проєктування повністю вичерпав свій ресурс і почав гальмувати будь-який подальший розвиток.
Кожна нова фіча вимагала все більше часу на рефакторинг і тестування, а швидкість завантаження сторінок почала небезпечно просідати в найнесподіваніших місцях, викликаючи шквал скарг. Ми витрачали робочі дні на пошук вузьких місць, оскільки початкова логіка була хаотично розмита між клієнтом і сервером без чіткого розуміння меж відповідальності. У той критичний момент стало абсолютно очевидно: якщо терміново не переглянути фундаментальний підхід до побудови системи, ми просто потонемо в нескінченному технічному борзі, а розробники почнуть масово вигоряти на підтримці legacy-коду.
Ця криза змусила нас зупинити процес хаотичного латання дірок і повністю переосмислити те, як саме ми структуруємо наші вебпроєкти. Нижче наведено список основних тригерів, які чітко сигналізують будь-якій команді розробників про те, що поточна архітектура вже застаріла і потребує негайного хірургічного втручання.
Головні симптоми та індикатори того, що стара архітектура вичерпала себе:
- Критичне зростання часу відгуку сервера (TTFB) та часу повного завантаження сторінки навіть за стабільної, прогнозованої кількості щоденних відвідувачів.
- Повний хаос у кодовій базі: бізнес-логіка почала дублюватися і на фронтенді, і на бекенді, через що внесення мінімальної правки перетворювалося на квест.
- Будь-яка локальна мікрозміна в інтерфейсі вимагала болючих, каскадних правок у структурі бази даних та контрактах API.
- Команда почала витрачати до 70% свого робочого часу на гасіння «пожеж» і підтримку старої системи замість розробки нових цінних фіч для бізнесу.
- Повна відсутність масштабованості: додавання нового функціоналу ламало сумісність старих модулів, створюючи ефект доміно.
Цей болісний досвід примусив нас радикально переглянути підхід до розподілу завдань усередині команди та переосмислити саму концепцію взаємодії різних шарів додатку. Про те, які саме помилки ми допустили на старті і як почали вибудовувати нову логіку, варто розповісти набагато детальніше, адже з цим стикається майже кожен технічний фахівець.
Чому моноліт чи стандартний підхід перестали працювати
Коли ми остаточно усвідомили, що поточна архітектура тріщить по швах, постало головне інженерне питання: де саме сталася системна помилка? Спочатку нам здавалося, що справа в неоптимізованих запитах до бази даних або у повільному роутингу. Проте глибокий внутрішній аудит показав набагато фундаментальнішу проблему — сама концепція нашого моноліту вичерпала себе. Ми намагалися врятувати ситуацію точковим рефакторингом, але це лише відклало неминуче кризове зіткнення з реальністю.
У класичному монолітному підході все виглядає просто й привабливо лише на старті: єдина кодова база, спільні моделі даних, мінімальний поріг входження для нових розробників. Але з часом такий моноліт неминуче перетворюється на «великий м’який клубок дротів» (Big Ball of Mud), де будь-яка зміна в одному модулі ламає логіку в зовсім іншій частині системи. Наша команда зіткнулася з тим, що фронтенд-розробники були повністю заблоковані станом бекенду, а релізи перетворилися на лотерею з високим ризиком падіння продакшну.
Щоб наочно показати, у який глухий кут ми зайшли порівняно з гнучкішими підходами, ми виділили ключові показники:
|
Критерій оцінки |
Старий підхід (Монолітний хаос) |
Новий підхід (Архітектурний редизайн) |
|
Швидкість викатки фіч |
Висока на старті, падає до нуля через накопичений техборг |
Стабільна, завдяки чіткій ізоляції компонентів |
|
Масштабування системи |
Тільки вертикальне (дороге і з фізичним лімітом) |
Горизонтальне, з можливістю розгортання окремих шарів |
|
Зони відповідальності |
Розмиті, розробники дублюють логіку на клієнті і сервері |
Чітко окреслені контракти між шарами |
|
Час дебагу помилок |
Довгий (важко знайти початкову точку збою) |
Швидкий, завдяки ізольованому логуванню |
Перерозподіл зон відповідальності: Де проходить межа
Головним інженерним викликом на шляху до оздоровлення нашої системи стало чітке переосмислення того, де саме закінчується робота клієнтської частини і починається серверна зона. До проведення редизайну у нас панував так званий «сірий туман»: фронтенд-розробники часто тягнули бізнес-логіку безпосередньо у компоненти інтерфейсу, а бекенд намагався керувати станом сторінок або надсилав надлишки сирих даних, змушуючи клієнт фільтрувати їх «на льоту». Детально про те, чим відрізняється бекенд і фронтенд розробка з погляду сучасної архітектури подібних проєктів, ми вже розповідали раніше, але на практиці цього разу нам довелося вибудовувати залізобетонні кордони, щоб зупинити цей хаос.
Нам довелося відмовитися від спроб «подружити все з усім» усередині однієї кодової бази і перейти на суворі контракти взаємодії. Бекенд став повністю незалежним постачальником даних та єдиним джерелом правди (Single Source of Truth), тоді як фронтенд перетворився на самостійну клієнтську програму, яка займається виключно станом інтерфейсу та відображенням. Нові правила розподілу відповідальності в нашій команді:
- Бекенд: Відповідає за безпеку, авторизацію, складні обчислення, роботу з базою даних, транзакції та жорстку валідацію вхідних бізнес-даних. Жодної генерації HTML чи покладання бізнес-правил на інтерфейс.
- Фронтенд: Відповідає виключно за рендеринг, управління локальним станом інтерфейсу (State Management), користувацький досвід (UX), кешування запитів на клієнті та первинну перевірку форм перед відправкою.
- Контракт взаємодії (API): Будь-яка комунікація між ними відбувається через чітко зафіксовані схеми, які унеможливлюють випадкову передачу зайвих або неструктурованих даних.
Щоб уникнути непорозумінь між командами розробників, ми перейшли на стандартизовані JSON-контракти. Наприклад, ось так тепер виглядає вивірена структура відповіді API для завантаження даних користувача, де фронтенд отримує повністю готові до відображення дані без необхідності їх «докручувати» на клієнті:
JSON
{
"status": "success",
"code": 200,
"data": {
"user_id": "usr_948192",
"profile": {
"first_name": "Олександр",
"last_name": "Коваленко",
"email": "[email protected]",
"avatar_url": "https://cdn.meowmatrix.digital/avatars/usr_948192.webp"
},
"permissions": [
"read:dashboard",
"write:settings"
],
"meta": {
"session_expires_at": "2026-06-06T12:00:00Z"
}
}
}
Такий підхід дозволив нам повністю позбутися залежності, коли зміна однієї кнопки на екрані вимагала переписування логіки на сервері. Кожна сторона тепер знає свої точні межі та виконує свою роботу, не створюючи вузьких місць для решти команди.
Вибір технологічного стека та реалізація на практиці
Після того як ми остаточно зафіксували нові правила гри та межі відповідальності між бекендом і фронтендом, постало логічне питання: за допомогою яких інструментів усе це втілювати в реальність? Старий стек технологій був навантажений застарілими бібліотеками, які тягнули за собою купу залежностей і гальмували розробку. Нам потрібен був компроміс між швидкістю викатки фіч, стабільністю під навантаженням та зручністю підтримки коду новими інженерами, які приєднуватимуться до команди.
Ми провели закритий технічний аудит і відмовилися від частини монолітних рішень на користь модульності та суворої типізації. Нам важливо було знизити кількість рантайм-помилок ще на етапі компіляції чи збірки, а також забезпечити стабільний обмін даними.
Щоб наочно показати зміну підходу до інструментарію, ми звели ключові зміни у порівняльну таблицю:
|
Компонент системи |
Старий технологічний стек |
Новий технологічний стек |
Чому зробили заміну |
|
Бекенд-фреймворк |
«Сирий» моноліт на базі застарілих бібліотек без типізації |
Сучасний асинхронний фреймворк із підтримкою DI |
Пришвидшення обробки запитів та ізоляція бізнес-логіки |
|
Передача даних |
Неструктуровані масиви, «сирий» JSON із дублюванням |
Суворі контракти, валідація через схеми (DTO) |
Уникнення помилок типу на клієнті через зміни в базі |
|
Фронтенд-архітектура |
Традиційний SSR із щільноюв’язкою логіки в шаблонах |
Компонентний підхід, кліентоорієнтований рендеринг |
Повна незалежність інтерфейсу від серверного рендерингу |
|
Контроль якості коду |
Ручне тестування, відсутність типізації на стиках шарів |
TypeScript на фронті, сувора типізація даних на бекенді |
Зменшення кількості багів на продакшні на 60% |
Практична реалізація та приклад коду
Під час практичної реалізації нового стека найважливішим завданням було забезпечити надійну валідацію даних на вході до бекенду. Ми відмовилися від ситуацій, коли сервер «вірить на слово» всьому, що надсилає фронтенд.
Для прикладу наведемо частину реального сервісу на бекенді (на прикладі TypeScript/Node.js середовища), де ми використовуємо чітку валідацію вхідного контракту через класи DTO (Data Transfer Object). Це гарантує, що жоден неваліжний запит не дійде до бази даних:
TypeScript
import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class UpdateUserProfileDto {
@IsNotEmpty({ message: "Ідентифікатор користувача обов'язковий" })
@IsString()
user_id: string;
@IsNotEmpty({ message: "Email не може бути порожнім" })
@IsEmail({}, { message: "Некоректний формат електронної пошти" })
email: string;
@IsNotEmpty({ message: "Ім'я обов'язкове для заповнення" })
@MinLength(2, { message: "Ім'я повинно містити щонайменше 2 символи" })
@IsString()
first_name: string;
}
// Приклад обробки запиту у контролері
export async function handleProfileUpdate(dto: UpdateUserProfileDto) {
// На цьому етапі ми на 100% впевнені у валідності даних
const updatedUser = await db.users.update(dto.user_id, {
email: dto.email,
first_name: dto.first_name,
updated_at: new Date()
});
return {
success: true,
data: updatedUser
};
}
Такий підхід дозволив нам закрити безліч вразливостей і позбутися паніки при зміні структури баз даних, адже будь-яка невідповідність контракту одразу відловлюється компілятором або валідатором.
Що змінилося після редизайну архітектури
Минуло вже кілька місяців відтоді, як ми повністю розгорнули нову архітектуру на продакшн і закрили старі монолітні хвости. Звісно, будь-який технічний редизайн чи рефакторинг має сенс лише тоді, коли його результати можна виміряти сухими цифрами, а не просто суб’єктивними відчуттями на кшталт «тепер код виглядає приємніше». Ми зібрали реальні метрики нашої системи до і після змін, щоб об’єктивно оцінити ефективність прийнятих рішень.
|
Метрика або показник |
До редизайну (Стара архітектура) |
Після редизайну (Нова архітектура) |
|
Середній час відгуку сервера (TTFB) |
|
|
|
Швидкість релізу нової фічі |
До 3 тижнів через каскадні баги та залежності |
|
|
Кількість багів на продакшні |
Висока |
Зменшилась на 65% |
|
Час онбордингу нового розробника |
|
До тижня (завдяки суворим DTO та типізації) |
Що дав цей перехід команді та бізнесу в довгостроковій перспективі
Окрім очевидних покращень у графіках системного моніторингу, ми відчули колосальні зміни в робочій атмосфері всередині команди. Інженери перестали боятися чіпати старі модулі, оскільки межі відповідальності між бекендом та фронтендом тепер чітко задокументовані й захищені на рівні типів і контрактів.
Бізнес отримав те, чого найбільше потребував — прогнозованість релізів. Запуск нових маркетингових ініціатив чи масштабних продуктових оновлень більше не супроводжується панікою та аварійним падінням продакшну вночі. Цей досвід остаточно переконав нас у тому, що інвестиції у правильне проєктування архітектури на старті зростання — це не витрата часу, а єдиний спосіб вижити на ринку високих навантажень.
Висновки та поради для команд, які планують подібний рефакторинг
Підсумовуючи весь цей тернистий шлях, я хочу звернутися до кожного колеги-розробника, тімліда чи архітектора, який зараз дивиться на свій роздутий моноліт і думає: «А чи не час усе переписати?». Повірте, ми теж пройшли через усі стадії прийняття неминучого — від заперечення («та воно ще попрацює») до торгів («ну давайте просто додамо пару костилів тут і там»). Але дива не станеться.
Головна помилка, якої я категорично раджу уникати на старті — це спроби зробити весь редизайн раптово, за один «великий ідеальний ривок» без підготовки. Наш власний гіркий досвід на початку цього проєкту навчив нас простої істини: якщо ви не зафіксували чіткі контракти взаємодії та межі відповідальності на папері до того, як відкрили редактор коду, ви просто перенесете старий хаос у новий технологічний стек.
Я чудово пам’ятаю момент, коли ми вперше спробували викатити оновлений модуль на продакшн без попередньої жорсткої синхронізації між бекенд- та фронтенд-командами. Здавалося б, дрібниця — ми змінили назву одного з полів у відповіді API, бо «так логічніше». У результаті в п’ятницю ввечері ми отримали повністю «лежачий» інтерфейс для кількох тисяч активних користувачів, обірвані сесії та кілька годин гасіння пожежі замість спокійного вікенду. Саме після того епізоду ми зрозуміли, що архітектура будується не на технологіях, а на залізобетонній дисципліні комунікації та суворих контрактах.
Ключові поради для тих, хто планує подібний архітектурний стрибок:
- Не бійтеся говорити бізнесу правду про техборг: Так, фаундери та менеджери хочуть нові фічі «навчора». Але якщо ви не витратите час на архітектурний фундамент зараз, через пів року розробка зупиниться взагалі через неможливість додати хоч щось нове.
- Проводьте межі відповідальності без компромісів: Бекенд має бути чистим джерелом даних та логіки, фронтенд — самостійним клієнтом. Не дозволяйте розмивати ці шари заради «тимчасової економії часу».
- Інвестуйте в типізацію та автоматизацію на стиках: TypeScript, DTO-валідатори та загальні схеми контрактів окупають себе вже на перший тиждень після релізу, економлячи сотні годин на пошуку людських помилок.
Буду щиро радий почути вашу думку в коментарях.
3 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівВ цілому текст ні про що.
Архітектурний редизайн починається зі ревізії вимог, для великих проектів ще й ревізії бізнес цілей.
В загальному випадку наведені болючі точки не є підставою для саме переробки архітектури, в статті не пояснили чому не не можна було їх вирішити в рамках поточної архітектури.
А ще стаття замовчує витрати на власне редизайн та його імлеметацію.
Я прочитам мельком, хотів щось написати. Потім подумав — а нафіга, кому воно потрібно, зараз таких статей по кілька на тиждень.
Раніше це був тривалий болісний процес написання статті, де автор намагався доступно і чітко сформулювати свої думки і описати досвід.
Зараз з допомогою АІ статті пишуться за хвилини. Ок, якщо є що сказати, то треба витратити більше часу — кілька годин. Ну так оті, які «кілька годин» — в них щось цікаве/корисне може бути, а в решті — вода.
Цікаво, що зараз багато людей називають шари (Layer/Tiers) монолітом.