Це має знати кожний розробник: навіщо потрібна архітектура ПЗ та які проблеми вона вирішує

Богдан Пархоменко

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

У процесі розробки програмного забезпечення ви можете зіткнутися з різними труднощами. Їх можна поділити на декілька категорій:

  • Швидкість. Розробку сповільнюють темпи тестування, додавання нових фіч, зміна існуючого функціоналу тощо.
  • Зрозумілість. У занадто великому проєкті складно знайти фрагмент коду, функцію, помилки. Навіть може бути незрозуміло, куди впроваджувати новий функціонал.
  • Гнучкість. Основна мета продукту — його бізнес-логіка. Якщо вона зав’язана на конкретну базу даних чи фреймворк, ця система не гнучка. У такому разі складно замінити БД чи фреймворк на інші. А при перенесенні коду може змінитися бізнес-логіка та навіть загубитися важливе для бізнесу правило.

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

Отже, згадані труднощі поділяються на такі групи:

  • Політики

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

  • Деталі

Говорять про те, як має відбуватися задумане на рівні політики. Деталі можуть описувати отримання даних із репозиторію, відображення таблиць, надсилання мейлів тощо.

Архітектура по своїй суті сконцентрована саме на розподілі політик і деталей.

Щоб визначити, до якої групи належить ваш код, подумайте: чи диктує код правила, як щось у застосунку має працювати? Якщо так, то це політика. Якщо ж код просто є реалізацією того, що має працювати, це деталі. До них відносяться, наприклад, фреймворки NestJS та Express.js або бібліотеки npm на кшталт Redux.

Багаторівнева архітектура

У такій архітектурі зазвичай є дві зони відповідальності, які не перетинаються.

Доменний рівень

Тут зосереджені політики:

  • бізнес-логіка;
  • правила;
  • сутності;
  • сервіси;
  • події.

На відміну від бібліотек, вони є незмінними в програмному забезпеченні. Тобто їх не можна замінити на щось інше, не змінивши суть самого продукту.

Інфраструктурний рівень

Цей прошарок архітектури призначений для деталей.

Це можуть бути:

  • контролери;
  • роути;
  • бази даних;
  • кеш;
  • сервіси для зовнішніх API;
  • ORM.

Саме вони змушують код працювати так, як того вимагають політики. Деталі на цьому рівні можна замінювати аналогами.

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

Правило залежності

Наступна схема відображає шлях запиту за всіма рівнями: від інфраструктурного до доменного і навпаки. Може здатись, що інфраструктура залежить від домену, а домен — від інфраструктури. Однак це не так.

Уся справа у правилі залежності: щось оголошене у зовнішньому колі не повинно згадуватись у коді внутрішнім колом. Це правило зберігається і при більшій кількості рівнів.

У будь-якому випадку код має вказувати тільки всередину діаграми — із зовнішніх рівнів на внутрішні. Таким чином код доменного рівня не може залежати від коду інфраструктури. При цьому код інфраструктурного рівня може залежати від коду рівня домену.

Порти та адаптери

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

Паралельно на зовнішньому рівні створюємо адаптери або конкретні класи та реалізації, використовуючи ті ж інтерфейси. Залишається лише поєднати це все на зовнішньому рівні. Розглянемо на прикладі, як це зробити.

Припустимо, вам треба створити механізм для надсилання користувачеві мейлів після реєстрації. Конфігурація листа та умова для її надсилання знаходяться на доменному рівні. Ці параметри стосуються того, що і коли має статися. Сама ж реалізація належить до інфраструктурного рівня. У цьому випадку ми описуємо, як має відбуватися надсилання листа:

У результаті можна написати інтерфейс для сервісу. Він використовуватиме лише інтерфейс, і ніде не буде реалізації на цьому рівні. Інтерфейс можна уявити як один фрагмент мозаїки:

Далі описуємо, як надсилатиметься лист за допомогою певної бібліотеки. Кожну таку реалізацію теж можна уявити як шматок мозаїки:

Потім з’єднуємо ці фрагменти в цілісну картину. Для цього можна надіслати до сервісу будь-яку реалізацію, яка імплементує інтерфейс. Вони ідеально підходять один одному, завдяки чому з’являються два інстанси. Кожен робитиме те саме, але створені вони за допомогою різних реалізацій:

Що нам дає правильно підібрана архітектура

Простота тестування

При правильному підході до побудови архітектури тестувати код не складно. Якщо дотримуватися правила залежності, то код доменного рівня матиме нуль залежностей та працюватиме з абстракціями. За рахунок цього його можна повністю перевірити.

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

Код доменного рівня простий у тестуванні. Ви можете створювати моки, реалізуючи інтерфейси із замоканими класами. При цьому у вас не буде залежностей.

Код інфраструктурного рівня використовує вебсервіси, сховища об’єктів, кеші. А це все ускладнює тестування. Хоча за рахунок зручності перевірки доменного рівня ви заощадите частину часу на тестах.

Гнучкість коду

У разі зміни політики ви можете впливати на деталі, адже вони залежать від політики. Проте зміна деталей ніколи не має впливати на політику.

Код повинен залишатися гнучким, щоб ви могли швидко змінювати деталі без втручання в головну бізнес-логіку програми. Чим вища гнучкість, тим вища надійність самого ПЗ.

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


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

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

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Останні статті

IT в Україні йде до свого фінального кінця. І потраплятимуть туди виключно за покликом душі

Коротко про українську IT-сферу у 2024 році Це коли на одну вакансію Middle розробника по…

26.03.2024

Блокчейн-розробка сьогодні: зарплати і перспективи на ринку праці

Формування криптовалютної галузі в Україні почалося ще у 2014 – саме тоді з'явилися перші стартапи,…

18.03.2024

Скільки рішень ухвалює розробник? Погляд новачка, який запускає продукт

Автор цього блогу — Python-девелопер Сергій Солдатов, який вирішив створити досить унікальний продукт. І це…

12.03.2024

Чи треба готуватись до співбесіди?

Думки шукачів діляться на: «так, однозначно» і «ні, не вартує, я все і так про…

04.03.2024

Відкладаєте до останнього? Що таке «синдром студента» і як з ним боротися

Синдром студента — це форма прокрастинації, яка полягає в тому, що людина, якій дали завдання,…

23.02.2024

Вчимося працювати з Git: основи конфігурації, гілки, додавання файлів та директорій

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

20.02.2024