← Усі статті

Як проєктувати систему, яка проживе 10 років

Чому системи вмирають не через технології, а через ранні архітектурні рішення — і як проєктувати зміни, а не функції: межі модулів, домен, дані, ADR і чек-лист на десятиліття.

Зміст

Більшість програмних систем не вмирають через технології. Вони вмирають через архітектурні рішення, прийняті в перші місяці розробки.

Коли проєкт стартує, здається, що головне — швидше випустити першу версію. Терміни тиснуть, інвестори чекають на демо, конкуренти вже в prod. Архітектор малює «просту» схему. Команда пише код, який «потім відрефакторимо». Через три роки «потім» перетворюється на «ніколи» — і будь-яка зміна коштує як міні-проєкт.

Але справжня вартість системи визначається не швидкістю розробки MVP. Вона визначається здатністю продукту адаптуватися до нових вимог без постійних переписувань, втрати даних і місяців простою бізнесу.

Чому одні системи успішно розвиваються десятиліттями — бухгалтерія в банку, складський облік у ритейлі, CRM у B2B — а інші перетворюються на дорогий технічний борг уже через кілька років? Відповідь майже ніколи не в «застарілому фреймворку». Відповідь — у тому, наскільки дешевою залишається кожна наступна зміна.

Ця стаття — для архітекторів, CTO, тімлідів і senior-розробників, які проєктують систему сьогодні і не хочуть через п'ять років пояснювати бізнесу, чому «додати одну кнопку» займає квартал.

Ключові висновки

Системи вмирають від зв'язаності, а не від Java vs Go. Чим сильніше модулі залежать одне від одного, тим дорожча кожна зміна — і тим швидше команда перестає встигати за бізнесом.

Проєктуйте зміни, а не функції. Питання «як реалізувати фічу X» важливе на спринті. Питання «що буде, якщо через три роки X зміниться» — важливе на десятиліття.

Домен живе довше технологій. Клієнт, замовлення, рахунок, поставка — переживуть зміну фреймворка, БД і UI. Архітектура має відображати предметну область, а не шари «controller / service / repository».

Універсальність — найдорожча пастка. Конструктори всього, EAV-таблиці й «налаштовувані поля для всього» виграють на демо й програють у продакшені.

Дані важливіші за код. Код можна переписати за півроку. Міграція десятирічної історії замовлень з кривою схемою — це роки й ризик для бізнесу.

Архітектура має пережити творців. Люди йдуть швидше, ніж змінюються технології. Зрозуміла структура й ADR дешевші, ніж «геніальні» абстракції, які розуміє одна людина.

Система живе довго не тому, що її створили ідеально. Вона живе довго тому, що кожна наступна зміна залишається достатньо дешевою.


Вступ

Архітектура — це не вибір між мікросервісами й монолітом на діаграмі в Miro. Це накопичений ефект рішень про межі, дані, залежності й компроміси — рішень, які в момент прийняття здаються дрібними.

«Давайте поки покладемо статус замовлення в спільну таблицю entities». «Зробимо один сервіс CommonService для всього». «Додамо налаштовувані поля — клієнт сам налаштує». Кожне таке рішення економить день на старті й забирає тиждень через три роки.

Довгоживучі системи об'єднує одна властивість: вартість зміни зростає повільно. Погані системи — навпаки: після певного порогу кожна нова фіча вимагає чіпати половину кодової бази, погоджувати з п'ятьма командами й молитися, щоб нічого не зламалося в prod.

Неможливо наперед вгадати, якою стане система через 10 років. Але можна побудувати її так, щоб зміни не перетворювалися на катастрофу. Саме про це — решта глав.


Глава 1. Чому більшість систем не доживають до 10 років

Типовий життєвий цикл проєкту

Життєвий цикл більшості корпоративних систем передбачуваний. Його корисно знати не як теорію, а як чек-лист раннього попередження: на якому етапі ви зараз і що буде далі, якщо нічого не міняти.

Етап 1. Ейфорія старту

Перші місяці — золотий час:

  • Швидка розробка. Команда з п'яти людей знає весь код. Рішення приймаються за обідом.
  • Проста архітектура. Один репозиторій, одна БД, один деплой. Діаграма поміщається на серветку.
  • Мінімум обмежень. «Зробимо правильно потім», «рефакторинг після MVP», «це тимчасово».

На цьому етапі velocity висока, багів мало, бізнес задоволений. Здається, що так буде завжди. Це ілюзія: простота — наслідок малого масштабу, а не хорошої архітектури.

Етап 2. Зростання вимог

Бізнес зростає — зростає й система:

  • Нові функції. Звіти, дашборди, мобільний застосунок, API для партнерів.
  • Нові ролі користувачів. Менеджер, бухгалтер, комірник, аудитор — у кожного свої права й сценарії.
  • Інтеграції. CRM, бухгалтерія, маркетплейси, банки, логістика.
  • Звіти. «Потрібен pivot по регіонах за останні три роки з групуванням по SKU».

Кожна нова можливість додається в існуючу структуру. Якщо межі модулів не були закладені, нові функції просто «приклеюються» до того, що вже є. Зв'язаність зростає лінійно з кожним релізом.

Етап 3. Поява перших костилів

Тимчасові рішення стають постійними:

  • Прапорець is_legacy_flow у таблиці замовлень, бо «новий процес поки лише для одного клієнта».
  • Копіпаста логіки розрахунку знижки в три модулі — «потім винесемо в спільний сервіс».
  • JOIN через сім таблиць для одного звіту — «оптимізуємо, коли буде час».

Костилі копіюються швидше, ніж рефакторять. Новий розробник не знає історії й повторює патерн. Через рік «тимчасове» рішення — єдиний спосіб, яким система взагалі працює.

Етап 4. Архітектурна криза

Симптоми впізнавані:

  • Будь-яка зміна ламає сусідні частини. Поправили розрахунок ПДВ — впали звіти по складу.
  • Швидкість розробки падає. Те, що раніше робили за спринт, тепер — за квартал.
  • Зростає кількість помилок. Регресії після кожного релізу. QA не встигає.
  • Команда боїться чіпати код. «Не знаємо, що зламається».

На цьому етапі бізнес уже втрачає гроші: конкуренти швидше виводять фічі, а ваші «прості доопрацювання» коштують як проєкти.

Етап 5. Велике переписування

Класичний фінал:

  • Рішення переписати систему з нуля — «на сучасному стеку, правильно».
  • Втрата часу й грошей: два-три роки паралельної розробки, подвоєна команда, міграція даних.
  • Повторення старих помилок — бо ті самі люди під тим самим тиском знову економлять на межах «до першого релізу».

Переписування рідко вирішує проблему назавжди. Воно скидає таймер — і цикл починається знову, якщо не змінився підхід до проєктування.

Ейфорія → Зростання → Костилі → Криза → Переписування → Ейфорія (v2) → ...
         ↑______________________________________________|
                    (якщо не міняти принципи)

Глава 2. Головний принцип довгоживучих систем

Проєктуйте зміни, а не функції

Більшість розробників проєктують систему під поточні вимоги. Product owner приносить user story — команда реалізує. Архітектура «складається» з відповідей на конкретні задачі спринту.

Але вимоги завжди змінюються. Закон, який сьогодні не стосується вашого продукту, через два роки стане обов'язковим. Клієнт попросить інтеграцію, про яку ніхто не думав. Ринок зміниться — і «очевидна» бізнес-модель застаріє.

Довгоживучі системи проєктують інакше: зміни мають бути дешевими. Не «ідеальними». Не «красивими». Дешевими — у сенсі часу, ризику й кількості зачеплених модулів.

Неправильне питання

Як реалізувати цю функцію?

Це питання правильне для задачі на два дні. Воно небезпечне як єдине архітектурне питання. Воно веде до рішень на кшталт: «швидше за все додати поле в існуючу таблицю» — без розуміння, що таблиця вже обслуговує чотири неспоріднені домени.

Правильне питання

Що станеться, якщо через три роки вимоги зміняться?

Конкретні формулювання, які варто ставити на design review:

Питання Навіщо
Які частини системи доведеться міняти, якщо зміниться правило X? Оцінка зв'язаності
Чи можна замінити модуль A, не чіпаючи модуль B? Незалежність
Де зберігається «істина» для цієї сутності? Єдине джерело даних
Що як завтра з'явиться другий канал продажів / друга валюта / другий склад? Розширюваність без переписування
Як відкотити цю зміну? Керованість ризику

Хороша архітектура — це архітектура, де типова зміна бізнесу локалізована. Додали новий тип документа — змінився один модуль. Змінили правило знижок — змінився один сервіс. Не половина моноліту.


Глава 3. Боріться зі зв'язаністю

Що таке зв'язаність

Зв'язаність (coupling) — це ступінь, у якій зміна в одному місці системи змушує міняти інші місця.

Низька зв'язаність: модуль «Білінг» змінює формат рахунку — модуль «Склад» не зачеплений.

Висока зв'язаність: зміна статусу замовлення вимагає правок у п'яти сервісах, трьох тригерах БД, двох cron-задачах і фронтенді — бо логіка «розмазана» по всій системі.

Зв'язаність — головний ворог довгоживучих систем. Не legacy-код. Не «старий» фреймворк. Саме зв'язаність робить legacy невиліковним без переписування.

Приклади високої зв'язаності

Спільні таблиці на всю систему

Одна таблиця documents для замовлень, рахунків, актів, заявок і внутрішніх memo. Відмінність — у полі type. Через три роки у ній 200 колонок, половина nullable, індекси не працюють, а бізнес-аналітик не може пояснити, які поля до чого стосуються.

Глобальні стани

Singleton-сервіс AppContext, через який половина модулів читає «поточного користувача», «поточну організацію», «поточні налаштування». Тестувати неможливо. Паралельні запити ламають одне одного.

Універсальні сутності

Entity + Attribute + Value — класичний EAV. «Опишемо все через метадані». На демо гнучко. У prod — JOIN на JOIN, немає типізації, валідація в runtime, звіти пишуться тижнями.

Спільні сервіси на всі випадки життя

CommonService, UtilsService, HelperService — звалище методів, яке використовують 40 модулів. Будь-яка зміна — ризик для всіх. Ніхто не знає, хто що викликає.

Наслідки

Наслідок Як проявляється
Неможливість локальних змін «Не можна чіпати модуль X — від нього залежить половина системи»
Зростання технічного боргу Костилі множаться, бо «правильний» рефакторинг занадто дорогий
Уповільнення команди Онбординг місяцями. Code review — лотерея
Крихкість Баг в одному місці — каскад збоїв
Страх змін Команда додає ще один шар обходу замість виправлення кореня

Як знижувати зв'язаність

  • Явні межі модулів з контрактами (API, події, інтерфейси).
  • Заборона на «зручний» доступ до чужих таблиць і внутрішніх класів.
  • Одне джерело істини для кожної бізнес-сутності.
  • Події замість прямих викликів там, де модулі мають реагувати, а не керувати одне одним.

Глава 4. Проєктуйте навколо бізнес-домену

Чому технічні шари недовговічні

За 10 років у типовому проєкті зміниться майже все технічне:

Шар Що змінюється
Мова програмування PHP → Java → Go → щось інше
База даних MySQL → PostgreSQL → розподілене сховище
Фреймворк Symfony → Laravel → NestJS → «наступний хайп»
UI jQuery → React → «серверні компоненти» → невідомо що
Інфраструктура bare metal → Docker → Kubernetes → serverless

Структура «controllers / services / repositories / models» не переживає зміну команди й технологій, бо нічого не каже про бізнес. Новий розробник бачить папки — і не розуміє, де закінчується «замовлення» й починається «склад».

Бізнес-сутності живуть довше

При цьому предметна область змінюється повільніше:

  • Клієнт — був, є й буде (хай і з іншими полями).
  • Замовлення — суть та сама: хто, що, коли, за скільки.
  • Рахунок — гроші, податки, термін оплати.
  • Поставка — звідки, куди, коли, статус.
  • Співробітник — ролі, права, оргструктура.

Десять років тому замовлення оформлювали по телефону. Сьогодні — через застосунок і маркетплейс. Замовлення залишилося замовленням. Змінилися канали й правила — не суть.

Домен живе довше технологій

Звідси практичний принцип: архітектура має будуватися навколо предметної області, а не навколо технічних шарів.

Погано (технічні шари):          Добре (доменні модулі):

src/                               src/
  controllers/                       orders/
  services/                            OrderService
  repositories/                        OrderRepository
  models/                              OrderStatus
  utils/                             billing/
                                       InvoiceService
                                     inventory/
                                       StockMovement

Модуль orders можна переписати з PHP на Go, не чіпаючи billing. Модуль inventory можна винести в окремий сервіс, коли навантаження зросте. Межі збігаються з мовою бізнесу — і їх розуміють не лише розробники, а й product owner, і аналітики.

Domain-Driven Design тут — не «модна методологія», а інструмент довголіття: bounded context, ubiquitous language, явні aggregate — усе про те, щоб код і розмови про продукт говорили однією мовою десятиліття.


Глава 5. Ніколи не робіть систему повністю універсальною

Найдорожча пастка

Бажання передбачити всі варіанти наперед — одна з найдорожчих помилок у корпоративній розробці. Вона приходить під благовидними іменами: «гнучка архітектура», «платформа», «конструктор процесів», «налаштування без програмістів».

На презентації це виглядає як competitive advantage. В експлуатації — як монстр, якого ніхто не може підтримувати.

Як з'являються монстри

Типові симптоми універсальності:

Патерн Обіцянка Реальність через 5 років
Конструктори всього «Будь-який процес без коду» 400 кроків у workflow, ніхто не розуміє, чому рахунок не виставився
Налаштовувані поля для всього «Клієнт сам додасть поле» EAV, звіти через VIEW, DBA у постійному стресі
Універсальні таблиці «Одна модель для всіх документів» 180 nullable-колонок, міграції — кошмар
Універсальні форми «Form builder в адмінці» Валідація в runtime, баги лише в prod
Універсальні процеси «BPM для будь-якого сценарію» Два паралельні процеси на один документ, race conditions

Чому це виглядає гарно на старті

Універсальність справді економить час у перші місяці:

  • Не потрібно писати окрему модель для кожного типу документа — «додамо тип у довідник».
  • Демо вражає: «дивіться, за п'ять хвилин створили нову сутність».
  • Продажі обіцяють «підходить будь-якому бізнесу».
  • Архітектор отримує схвалення на «масштабоване рішення».

Це кредит під високий відсоток. Відсотки нараховуються в запитах до БД, у шарах абстракції, у головах розробників — і в простої бізнесу, коли «проста зміна» займає місяць.

Чому це стає кошмаром через кілька років

  1. Продуктивність. Універсальні запити не індексуються так само добре, як типізовані.
  2. Втрата типізації. Помилки їдуть у prod — «поле було рядком, а потрібно число».
  3. Неможливість рефакторингу. Логіка розмазана по метаданих, конфігах і «універсальному рушію».
  4. Залежність від «магів». Лише дві людини розуміють, як працює конструктор — обидві вже звільнилися.
  5. Бізнес втрачає контроль. «Ми не можемо змінити процес — це зламає трьох клієнтів».

Правило: 80% системи — типізований домен з явними правилами. 20% — контрольовані точки розширення (плагіни, webhooks, користувацькі поля на периферії). Не навпаки.

Докладніше про ціну універсальності в ERP — у статті «Найдорожча помилка в архітектурі ERP».


Глава 6. Дані важливіші за код

Код можна переписати

Код — це інтерпретація бізнес-правил конкретною мовою й фреймворком. Його можна:

  • переписати іншою мовою;
  • розбити на мікросервіси;
  • замінити AI-генерацією (жарт, але тренд зрозумілий).

Дані — це історія бізнесу. Замовлення за десять років. Платежі. Аудиторський слід. Домовленості з клієнтами, зафіксовані в системі.

Дані майже неможливо переписати

Міграція даних — найдорожча й найризикованіша операція в житті системи:

  • Втрата даних = судові позови, штрафи, втрата довіри.
  • Некоректна міграція = звіти не сходяться, бухгалтерія в паніці.
  • Довга міграція = місяці паралельної роботи двох систем.

Погана модель даних переживає будь-яку кількість рефакторингів коду — бо «чіпати БД страшно».

Помилки проєктування даних

Непродумані зв'язки

Foreign key на «універсальну» таблицю без розуміння життєвого циклу. Cascade delete, який зносить половину історії. Зв'язок many-to-many без проміжної сутності з метаданими.

Відсутність історії змін

«Просто оновимо поле status». Через рік ніхто не знає, хто й коли перевів замовлення в «скасовано» — а це потрібно для суперечки з клієнтом або аудиту.

Зберігання бізнес-логіки в даних

Прапорець skip_validation = true. Магічне число в полі type = 7. JSON з правилами, які інтерпретує «універсальний рушій». Логіка в даних не версіонується й не тестується так само, як код.

Універсальні EAV-структури

-- Гарно на діаграмі. Кошмар у SELECT.
entity_id | attribute_code | value_text | value_number | value_date
----------+----------------+------------+--------------+-----------
1001      | customer_name  | ТОВ Рога   | NULL         | NULL
1001      | amount         | NULL       | 15000.00     | NULL
1001      | due_date       | NULL       | NULL         | 2026-07-01

Три рядки замість одного типізованого запису. Звіт «усі замовлення з сумою > 10 000» — pivot і молитва.

Як проєктувати дані на десятиліття вперед

  1. Модель відображає бізнес, а не UI. Таблиця orders, а не screen_form_data.
  2. Append-only історія для критичних сутностей: audit log, event sourcing для статусів, valid_from / valid_to для довідників.
  3. Явні типи й обмеження в БД — not null, check constraints, enums там, де домен стабільний.
  4. Стратегія міграцій з першого дня — інструмент (Flyway, Liquibase, Alembic), правила іменування, rollback-план.
  5. Розділення «операційних» і «аналітичних» даних — не ганяти OLAP-запити по prod-таблицях замовлень.
  6. Документування інваріантів — «сума рядків замовлення = total», «не можна видалити оплачений рахунок» — в ADR і в constraints, де можливо.

Глава 7. Будуйте систему з незалежних модулів

Що таке справжній модуль

Модуль — не папка в проєкті.

Модуль — не namespace.

Модуль — не набір файлів із спільним префіксом.

Модуль — це частина системи, яку можна міняти незалежно — з мінімальним ризиком для решти частин і зі зрозумілим контрактом назовні.

Тест на модуль: «Чи можемо ми замінити реалізацію модуля Billing, не перекомпілюючи Orders?» Якщо ні — це не модулі, а компоненти одного моноліту з ілюзією структури.

Ознаки хорошого модуля

Ознака Що це означає
Власні дані Модуль володіє своїми таблицями / колекціями. Інші читають через API, не через прямий SQL
Власні правила Бізнес-логіка модуля не розмазана по Utils
Мінімум залежностей Залежить від контрактів, а не від реалізацій сусідів
Явний публічний API Зрозуміло, що можна викликати ззовні, а що — внутрішнє
Незалежне тестування Модуль тестується ізольовано, з mock-залежностями

Ознаки поганого модуля

  • Спільна база логікиBaseEntity, AbstractDocument, від якого наслідується все.
  • Спільні таблиці — модуль «Склад» пише в таблиці модуля «Замовлення».
  • Постійні перехресні виклики — A викликає B, B викликає C, C викликає A.
  • Shared database як інтеграція — антипатерн «integration via database».

Modular monolith — розумний компроміс

Не обов'язково одразу йти в мікросервіси. Modular monolith — один деплой, але жорсткі межі модулів всередині:

┌─────────────────────────────────────────────┐
│              Modular Monolith               │
│  ┌─────────┐  ┌─────────┐  ┌─────────────┐  │
│  │ Orders  │  │ Billing │  │  Inventory  │  │
│  │ (API)   │  │ (API)   │  │  (API)      │  │
│  └────┬────┘  └────┬────┘  └──────┬──────┘  │
│       │ events     │ events       │         │
│       └────────────┴──────────────┘         │
└─────────────────────────────────────────────┘

Коли модуль дозріє — його можна винести в окремий сервіс без переписування домену. Межі вже є.


Глава 8. Робіть архітектуру зрозумілою для нових розробників

Чому люди змінюються швидше технологій

За 10 років у типовій команді:

  • Піде архітектор, який «усе задумував».
  • Піде тімлід, який знав усі костилі.
  • Команда зміниться двічі-тричі повністю.
  • Прийдуть джуни, які через рік стануть мідами — і прийматимуть архітектурні рішення.

Система має пережити своїх творців. Якщо лише Вася розуміє, як працює модуль оплати — це не архітектура, а bus factor = 1.

Ознаки зрозумілої архітектури

  • Очевидні правила. «Замовлення не імпортують Billing напряму — лише через BillingPort». Одне правило, одне місце в документації.
  • Одноманітні рішення. Усі модулі влаштовані однаково: domain / application / infrastructure. Не «тут так, там інакше, бо Петро так писав».
  • Проста навігація по коду. Нова людина за день знаходить, де створюється замовлення, де змінюється статус, де генерується рахунок.
  • Мінімум магії. Немає framework, який «сам усе inject'ить» без розуміння. Немає code generation, який ніхто не читає.

Ціна «розумних» рішень

Складні патерни — не ознака зрілості. Часто це ознака того, що архітектор хотів показати експертизу:

«Розумне» рішення Прихована ціна
Generic repository на все Втрата типізації, незрозумілі запити
Event sourcing скрізь Складність без виграшу для CRUD
CQRS для форми з трьох полів Два сховища, eventual consistency заради галочки
Plugin system з першого дня Ніхто не пише плагіни, усі лізуть у core

Простота — не примітивність. Проста архітектура, яку розуміє команда з восьми людей, переживе «геніальну», яку розуміє один.

Правило: якщо рішення не можна пояснити product owner за п'ять хвилин — воно занадто складне для поточного масштабу.


Глава 9. Документуйте рішення, а не код

Документація, яка справді потрібна

Код сам себе не пояснює — він пояснює що робить система. Не пояснює чому.

Через три роки ніхто не згадає, чому обрали PostgreSQL замість MongoDB. Чому статус замовлення — enum, а не рядок. Чому інтеграція з банком — синхронна, а не через чергу.

Марно

  • Коментарі до кожного методу// returns user by id над getUserById.
  • Опис очевидного — «цей клас представляє замовлення».
  • Wiki, яку ніхто не оновлює — застаріла документація гірша за відсутню.

Корисно

  • Чому прийнято рішення — контекст, обмеження, тиск термінів.
  • Які альтернативи розглядалися — і чому відкинули.
  • Які обмеження існують — «не можна міняти формат ID — від нього залежать зовнішні інтеграції».
  • Що буде, якщо порушити правило — «прямий доступ до таблиці orders з модуля reports зламає блокування».

Architecture Decision Records (ADR)

ADR — короткий документ на кожне значуще архітектурне рішення:

# ADR-007: Статус замовлення як enum в БД

## Статус
Прийнято

## Контекст
Потрібна фіксація життєвого циклу замовлення. Розглядали рядок,
JSON у metadata, окрему таблицю transitions.

## Рішення
Enum order_status в PostgreSQL + таблиця order_status_history
для аудиту.

## Наслідки
+ Типізація, прості запити
+ Явна історія
- Новий статус = міграція (прийнятно: статуси змінюються рідко)

ADR зберігаються в репозиторії поруч із кодом (docs/adr/). Вони версіонуються, їх бачить кожен розробник під час onboarding. Це архітектурна пам'ять проєкту — те, що переживає відхід людей.


Глава 10. Проєктуйте систему для команди майбутнього

Через 10 років розробники будуть іншими

Вони не знатимуть:

  • Чому зроблено саме так — «так історично склалося» не приймається.
  • Які були обмеження — «нам потрібен був реліз до Black Friday 2023».
  • Які компроміси приймалися — «ми знали, що EAV — погано, але клієнт платив за демо через два тижні».

Якщо ця інформація не зафіксована — команда майбутнього або боїться міняти код, або ламає систему, намагаючись «зробити правильно» без контексту.

Архітектура має пояснювати себе сама

Практичні принципи:

Принцип Приклад
Передбачувана структура Кожен модуль: domain/, application/, infrastructure/
Мінімум магії Явна реєстрація залежностей, не «auto-scan усього»
Максимум прозорості Логування на межах модулів, trace id крізь запит
Convention over configuration Один спосіб робити речі — не п'ять
Fail fast Порушення контракту — помилка при старті, не в prod у п'ятницю

Onboarding-тест: новий розробник за два тижні (не два місяці) може:

  • знайти місце для типової задачі;
  • зрозуміти, які модулі зачепить зміна;
  • написати код, який пройде review без «у нас так не роблять».

Якщо onboarding займає місяці — архітектура вже старіє, навіть якщо код «працює».


Глава 11. Як зрозуміти, що архітектура старіє

Ранні симптоми

Архітектурне старіння — не подія, а процес. Його можна помітити за метриками й поведінкою команди.

Кожен новий модуль стає складнішим за попередній

Перший модуль — 500 рядків. П'ятий — 5000, бо «потрібно врахувати всі випадки» і «перевикористати» напівмертвий CommonCore.

Будь-яка зміна вимагає правок у кількох місцях

User story «додати поле в картку клієнта» перетворюється на задачу на 13 файлів у 4 модулях.

Час розробки зростає при тому ж розмірі команди

Velocity падає рік до року. Story points зростають. «Раніше робили за спринт» — «тепер за квартал».

Кількість багів збільшується

Регресії. Hotfix'и після кожного релізу. «Ми не чіпали цей модуль» — «але він зламався».

Команда боїться рефакторингу

«Не чіпай — працює». «Зробимо обхідний шлях». «Ще один if». Технічний борг зростає, бо погашення здається дорожчим, ніж новий костиль.

Що робити

  • Вимірювати вартість змін — скільки файлів / модулів зачіпає типова фіча.
  • Architecture fitness functions — автоматичні перевірки: «модуль A не імпортує модуль B напряму».
  • Регулярні architecture reviews — раз на квартал, не «коли вже горить».
  • Локальний рефакторинг — не «велике переписування», а постійне покращення меж.

Глава 12. Практичний чек-лист системи на 10 років

Використовуйте як checklist на design review або під час аудиту існуючої системи.

Архітектура

  • Є чіткі межі модулів — намальовані, задокументовані, перевіряються в CI
  • Немає глобальних залежностей — немає GlobalContext, AppState, «зручного» доступу до всього
  • Немає універсальних сутностей заради універсальності — кожна сутність обґрунтована доменом
  • Інтеграція через контракти — API, події, порти — не через shared DB
  • Modular monolith або свідомі сервіси — не «розподілений моноліт»

Дані

  • Модель відображає бізнес — таблиці й сутності говорять мовою домену
  • Є стратегія міграцій — інструмент, процес, rollback
  • Є історія змін — audit log / event log для критичних сутностей
  • Constraints в БД — не лише валідація в коді
  • Розділення OLTP і аналітики — або план на це

Команда

  • Новий розробник може розібратися за тижні, а не місяці
  • Архітектурні рішення задокументовані — ADR, README модулів
  • Єдині conventions — структура, naming, code review checklist
  • Bus factor > 1 — критичні модулі знають мінімум двоє

Розвиток

  • Зміни локалізовані — типова фіча зачіпає 1–2 модулі
  • Нові функції не вимагають переписування старих
  • Є метрики вартості змін — і вони не зростають неконтрольовано
  • Рефакторинг — частина процесу, а не «коли-небудь потім»

Глава 13. Що справді робить систему довгоживучою

Не технології

Система не живе 10 років тому, що обрали:

  • «правильну» мову програмування;
  • «найкращу» базу даних;
  • «модний» фреймворк.

Усе це змінюється. Системи на COBOL і Mainframe досі працюють — не через технології, а через стійкість до змін (або тому, що міняти страшніше, ніж терпіти).

А здатність змінюватися

Система живе довго тому, що кожна наступна зміна залишається достатньо дешевою:

  • бізнес може адаптуватися до ринку;
  • команда може доставляти фічі без квартальних проєктів на «одну кнопку»;
  • дані зберігаються й накопичують цінність;
  • нові люди можуть входити в проєкт без піврічного навчання.

Це й є головний критерій хорошої архітектури. Не краса діаграми. Не кількість патернів. Не відповідність останньому blog post від ThoughtWorks.

Вартість типової зміни через 5 років має бути порівнянною з вартістю через 1 рік — хай і вища, але не на порядок.

Якщо через три роки «проста» фіча коштує в десять разів дорожче, ніж на старті — архітектура вже програла. Технології тут ні до чого.


Висновок

Архітектура — не мистецтво передбачати майбутнє. Ніхто не знає, чи стане ваш продукт маркетплейсом, SaaS-платформою чи нішевим інструментом для однієї галузі.

Архітектура — мистецтво зменшувати вартість помилок майбутнього:

  • проєктувати зміни, а не лише функції;
  • боротися зі зв'язаністю — головним вбивцею систем;
  • будувати навколо домену, а не технічних шарів;
  • не робити систему універсальною — це найдорожча пастка;
  • берегти дані — вони переживуть будь-який код;
  • ділити на незалежні модулі з явними контрактами;
  • робити архітектуру зрозумілою для людей, яких ви ще не найняли;
  • документувати рішення — ADR, а не коментарі до кожного рядка;
  • стежити за симптомами старіння — і лагодити межі, а не чекати «великого переписування».

Неможливо побудувати ідеальну систему з першого разу. Але можна побудувати систему, де помилки не накопичуються експоненційно — де кожен спринт не удорожчує наступний.

Саме це відрізняє довгоживучі системи від проєктів, які доводиться переписувати кожні кілька років — знову й знову, щоразу обіцяючи собі «цього разу зробимо правильно».

Запитайте себе не «як швидше випустити MVP», а «скільки коштуватиме змінити це через три роки». Відповідь на це питання — і є архітектура на десятиліття.