Cat sitting in front of laptop and stares at the monitor
Розбиратися в архітектурі хоча б на базовому рівні варто кожному розробнику. Нехай моя стаття стане відправною точкою у вивченні цієї теми. Я розповім, які проблеми у розробці вирішує правильно підібрана архітектура, а також опишу її реалізацію за допомогою портів та адаптерів.
У процесі розробки програмного забезпечення ви можете зіткнутися з різними труднощами. Їх можна поділити на декілька категорій:
Причина цьому — те, що код стає надто зв’язаним, як клубок ниток. Вдало підібрана архітектура допоможе уникнути подібних проблем. Для спрощення ситуації слід саме на рівні архітектури «розплутати» цей клубок.
Отже, згадані труднощі поділяються на такі групи:
Відображають бізнес продукту, що розробляється. Політика вирішує проблеми користувача та каже, що і коли має відбуватися. Саме для цього створюється кінцевий продукт.
Говорять про те, як має відбуватися задумане на рівні політики. Деталі можуть описувати отримання даних із репозиторію, відображення таблиць, надсилання мейлів тощо.
Архітектура по своїй суті сконцентрована саме на розподілі політик і деталей.
Щоб визначити, до якої групи належить ваш код, подумайте: чи диктує код правила, як щось у застосунку має працювати? Якщо так, то це політика. Якщо ж код просто є реалізацією того, що має працювати, це деталі. До них відносяться, наприклад, фреймворки NestJS та Express.js або бібліотеки npm на кшталт Redux.
У такій архітектурі зазвичай є дві зони відповідальності, які не перетинаються.
Тут зосереджені політики:
На відміну від бібліотек, вони є незмінними в програмному забезпеченні. Тобто їх не можна замінити на щось інше, не змінивши суть самого продукту.
Цей прошарок архітектури призначений для деталей.
Це можуть бути:
Саме вони змушують код працювати так, як того вимагають політики. Деталі на цьому рівні можна замінювати аналогами.
Для базового розуміння принципу побудови архітектури розберемо простий приклад. На діаграмі, що зображена нижче, зверніть увагу на півкулі, які розташовані на перетині між рівнями. Ці елементи призначені для написаних на доменному рівні інтерфейсів, які мають бути реалізовані лише на рівні інфраструктури. Ці півкулі є портами та адаптерами і допомагають дотримуватися правила залежності.
Наступна схема відображає шлях запиту за всіма рівнями: від інфраструктурного до доменного і навпаки. Може здатись, що інфраструктура залежить від домену, а домен — від інфраструктури. Однак це не так.
Уся справа у правилі залежності: щось оголошене у зовнішньому колі не повинно згадуватись у коді внутрішнім колом. Це правило зберігається і при більшій кількості рівнів.
У будь-якому випадку код має вказувати тільки всередину діаграми — із зовнішніх рівнів на внутрішні. Таким чином код доменного рівня не може залежати від коду інфраструктури. При цьому код інфраструктурного рівня може залежати від коду рівня домену.
По суті правило залежності слідує правилу інверсії залежностей. Проте цілком реально обійти залежність інфраструктури від домену. Для цього нам і потрібні порти з адаптерами. В такому разі на внутрішньому рівні ми створюємо порти у вигляді інтерфейсів та абстрактних класів, які використовують ці самі інтерфейси.
Паралельно на зовнішньому рівні створюємо адаптери або конкретні класи та реалізації, використовуючи ті ж інтерфейси. Залишається лише поєднати це все на зовнішньому рівні. Розглянемо на прикладі, як це зробити.
Припустимо, вам треба створити механізм для надсилання користувачеві мейлів після реєстрації. Конфігурація листа та умова для її надсилання знаходяться на доменному рівні. Ці параметри стосуються того, що і коли має статися. Сама ж реалізація належить до інфраструктурного рівня. У цьому випадку ми описуємо, як має відбуватися надсилання листа:
У результаті можна написати інтерфейс для сервісу. Він використовуватиме лише інтерфейс, і ніде не буде реалізації на цьому рівні. Інтерфейс можна уявити як один фрагмент мозаїки:
Далі описуємо, як надсилатиметься лист за допомогою певної бібліотеки. Кожну таку реалізацію теж можна уявити як шматок мозаїки:
Потім з’єднуємо ці фрагменти в цілісну картину. Для цього можна надіслати до сервісу будь-яку реалізацію, яка імплементує інтерфейс. Вони ідеально підходять один одному, завдяки чому з’являються два інстанси. Кожен робитиме те саме, але створені вони за допомогою різних реалізацій:
При правильному підході до побудови архітектури тестувати код не складно. Якщо дотримуватися правила залежності, то код доменного рівня матиме нуль залежностей та працюватиме з абстракціями. За рахунок цього його можна повністю перевірити.
Однак перед тим, як заходити надто далеко, запитайте себе: чи зможете «познущатися» з написаного коду? Якщо ви посилаєтесь на інтерфейси, слідкуєте за кодом і робите його за всіма принципами та архітектурою, то відповідь — «так». Адже код легко протестувати. В іншому випадку відповідь буде негативною.
Код доменного рівня простий у тестуванні. Ви можете створювати моки, реалізуючи інтерфейси із замоканими класами. При цьому у вас не буде залежностей.
Код інфраструктурного рівня використовує вебсервіси, сховища об’єктів, кеші. А це все ускладнює тестування. Хоча за рахунок зручності перевірки доменного рівня ви заощадите частину часу на тестах.
У разі зміни політики ви можете впливати на деталі, адже вони залежать від політики. Проте зміна деталей ніколи не має впливати на політику.
Код повинен залишатися гнучким, щоб ви могли швидко змінювати деталі без втручання в головну бізнес-логіку програми. Чим вища гнучкість, тим вища надійність самого ПЗ.
Ще на початковій стадії створення застосунку подумайте про механізми гнучкості. Коли продукт стане великим і навантаженим, запровадити їх буде значно складніше. Адже вам доведеться змінювати дуже багато елементів коду.
Порада наостанок — завжди шукайте баланс. Якщо ви, наприклад, створюєте простий скрипт для парсингу однієї сторінки новин, навряд вам знадобиться гнучкість архітектури в повному обсязі. Написання таких механізмів займе більше часу, ніж робота над скриптом.
Однак якщо скрипт виріс і парсить вже сотню сайтів, вам життєво необхідно впровадити якнайбільше механізмів гнучкості. Тому важливо правильно оцінювати перспективи розробки. Тільки так ви зрозумієте, чи потрібно в принципі продумувати архітектуру та приділяти багато уваги описаним мною рівням, політикам і деталям.
Блогер та розробник Джозеф Круз розповів, чому не варто писати ідеальний код та чому це…
Днями я завзято нила про щось ChatGPT (експериментую між сеансами з живим терапевтом). І от…
«Крутіть колесо, щоб отримати знижку до 50%!» «Натисніть тут, щоб відкрити таємничу пропозицію!» «Зареєструйтесь зараз,…
Дуже хочеться робити якісь десктопні апки. Сумую за часами коли всі програми були offline-first, і…
Надсилаючи криптовалюту, багато новачків ставлять запитання: як працюють комісії та чому вони відрізняються в різних…
Нова афера набирає обертів — ось детальний розбір того, як фальшиві потенційні роботодавці намагаються вкрасти…