Clear Architecture для бекенда на JS

Кожен хто займається розробкою певний час приходить до слова архітектура. Сьогодні ми подивимось на одну з парадигм проектування архітектури через призму типового JS бекенду.

Автор: Avi Werde. Опубліковано на Unsplash

Все почалось коли стало погано

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

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

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

Як виглядатиме переписаний проект з нуля

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

Тепер він в мене є

Архітектура - це набір правил для проектування сервісів і технологій. Який допомагає систаматезувати часто використовуємі практики.

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

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

Сьогодні ми поговоримо про один з видів архітектури поширений в розробці на C# або android. Але ми спробуємо його використати для JS бекенду, бо цей підхід має загальні рекомендації і не сильно прив'язаний до певної мови програмування.

Чиста Архітектура

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

Clear Architecture

Автор статті пропонує розділити додаток на декілько шарів.

  1. Frameworks

  2. Interface Adapters

  3. UseCases

  4. Entity

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

Interface Adapters - це шар відповідальний за передачу данних до наших use cases і потім за перетворення отриманих відповідей для запитуючого. Тут знаходяться контроллери і презентори

UseCases - це шар на якому знаходяться сценарії виростання.

Entity - це шар який акамулює всі бізнес правила стосовно сутностей нашого додатку

Одне з найважливіших правил яке надає ця архітектура це правило залежностей модулів один від одного. Напрямок залежностей повинен йти в зворотній бік. Тобто Entity не залежить невідкого, UseCases залежить від Entity, Interface Adapters залежить від UseCases, Frameworks залежить від Interface Adapters

Куди я попав?

Так склалось що у вільному доступі дуже мало інформації про адаптації подібного підходу в JS бекенд. Але багацько прикладів є в бекендах на С#(не тільки в бекендах). З цього ми можемо зробити висновок що ця архітектура підходить для бекенда. Для експеримента я обрав Nest.js. Чому? Тому що в ньому вже є реалізація DI і нам не доведеться реалізовувати це власноруч(Ну і я просто люблю Nest.js і вважаю його найкращім фреймворком для створення бекенда)

❗️ Disclaimer: Все наведене нижче це експеримент. Ви можете його модернизувати, але не раджу використовувати все це без застережно на production

Почнемо з самого головного

З folder structure. Так як вся ця архітектура прийшла з компільованих мов програмування, то кожен з шарів відокремлювався в окремий проект, який компілювався окремо. Саме це дає один з переваг архітектури. Дає їй певну стійкість. А також певну ізоляцію. В нас же мова програмування інтерпретована. І тому ми вільні від проблеми пересборки проекту. Але зберегти цю гарну структуру не буде зайвим. Дозволяє тріщки більше часу подумати над змінами які змушують нас редагувати декілька шарів.

Clean Architecture folder structure

Папки для Framework layer в нас немає. Чому? Справа в тому що саме цей шар відповідає за зовнішню взаємодію нашого додатку. Якщо б ми писали на чистому node.js ми б створювали подібну папку в якій би описували процес передачі запитів на наші контроллери. Але так як нес робить це за нас нам немає необхідності створювати подібну папку.

Поїхали далі. Domain - це папка для шару Entity. Тут зберігаються всі дані про наші сутності(Entity, Value Object, Exception, Repository Interfaces).

Application - це папка для шару UseCases. Тут будуть зберігатись сервіси які будуть реалізовувати use cases. Тобто сценарії використання.

Infrustructure - це папка для шару Interface Adapters. Тут будуть зберігатись все що пов’язано з роботою з базою наприклад, або з сторонніми сервісами

Presentation - це теж папка яка є частиною Interface Adapters Layer. Тут будуть наші контроллери які будуть викликати сервіси із Application Layer. А також тут можуть бути презентори.

Ось це 4 корневі папки які ми будемо використовувати для розділенні нашого кода. В сумі маємо 2 папки на Interface Adapters Layer, 1 для Entity Layer і одну на Application Layer

Знов todo?

Так знов. Це найкращій спосіб щось зрозуміти не зосереджуючись на деталях предметної області.


Подивимсь на те як виглядає Entity Layer. Як я вже казав трошки раніше цей шар застосунку відповідає за реалізацію бізнес сутностей. Це такі сутності які уособлють собою предметну область нашого проекту. Наприклад одною з корневих Entity для інтернет магазину є продукт. Тобто якби ми робили інтернет магазин в цій папці в нас знаходився файл з описом сутності “продукт”. Так як ми робимо ToDo ліст то тут в нас будуть знаходитись todo-item

Entity Layer

Так виглядає folder structure. Нічого зайвого чого не потрібно знати нашій сутності. Що тут ще може бути окрім сутностей? Наприклад shared компоненти які загальні для всіх entity

Тепер подивимось на реалізацію найголовнішого що в нас є на цьому шарі на самому класі сутності.

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

Кастомний ексепшн

Ще одним важливим моментом є те що всі задачі по зміні стейта всередині entity повинен займатись тільки сам entity. Ніхто зовні не повинен впливати на зміни всередині.

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

Application

Переходимо до другого леєра. Тут в нас знаходяться наші useCases. Тут будуть сервіси які будуть реалізовувати всі сценарії взаємодії з нашими ентіті.

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

Application layer folder structer

Подивимось що в нас відбувається у в головному класі цього шару. В класі сервіса.

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

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

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

Infrustructure

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

Folder structure

Як ви бачите тут також присутні кастомні ексепшени, які відносяться до цього леєру. Ось як виглядає репозиторій для todo-item

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

Presentation

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

Folder Structure

Ну і сам контроллер не є чимось складним просто виклик необхідної функції з сервісу

Todo Item Controller

Залежності

Як я казав на початку одна з ідей цієї архітектуру це залежності в проекті. На картинці напочатку напрямок залежностей йде з зовні в середину. Тобто Domain Layer який в середині не залежить не від кого. Далі Application залежить від Domain і тільки. Далі Infrustructure і Presentation залежать від Application, а значить і залежить від Domain.

Щоб зберегти напрямок залежностей для Infrustructure ми використали механізм dependency inversion. Таким чином ми можемо використовувати репозиторії у application layer, але application layer не залежить від шару інфраструктури.

Тестування

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

Висновки

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

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Владислав Лєсной
Владислав Лєсной@unnsin

563Прочитань
4Автори
17Читачі
На Друкарні з 15 квітня

Більше від автора

  • Паттерн “Репозиторій“ в JS

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

    Теми цього довгочиту:

    Js
  • Паттерн “Специфікація“ в JS чи потрібен він?

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

    Теми цього довгочиту:

    Js

Вам також сподобається

Коментарі (4)

Мені подобається в ентіті і конструктор і низка функцій. Я вважав що в ентіті мають бути самі параметри а мінімальна реалізація (до юзкейсів) як то toMap чи toJson якісь то в моделі. Але не пом’ятаю навіщо це сепарувати. Може то для фронтенда так…

Вам також сподобається