Словник Clean Architecture і Hexagonal Architecture
Очищення шарів архітектури, портів і адаптерів, правила залежностей, випадків використання і словника меж архітектури.
Програмна архітектура має свій власний точний словник. Коли ваша команда обговорює питання про те, чи має бізнес- логіка бути в контролері, або чи не протікає ORM бази даних у вашу модель домену, вам потрібні правильні слова, щоб зробити аргументи зрозумілими. Цей посібник містить основні англійські слова, які використовуються у контексті чистої архітектури, гексагональної архітектури і пов’ язаних з ними шаблонів, щоб ви могли впевнено обговорювати принципи проектування під час перегляду коду, сеансів проектування і інтерв’ ю з інженерами.
Основні терміни
** Чиста архітектура ** — багатошаровий архітектурний стиль, популяризований Робертом К. Мартін (Дядько Боб) у своїй 2017 році книзі з тією ж назвою. Він організовує код в концентричні кола, з найбільш стабільним, бізнес-критичним кодом в центрі і специфічними для фреймворку деталями на зовнішній стороні. Метою є забезпечення незалежності основної логіки від баз даних, структур користувацького інтерфейсу і зовнішніх служб.
«Ми повинні переробити цей модуль — бізнес-логіка безпосередньо пов’язана з базою даних. Давайте застосуємо чисту архітектуру і висунумо проблему персистентності назовні»
“У чистій архітектурі, внутрішні шари нічого не знають про зовнішні. Ваша модель домену ніколи не повинна імпортувати анотацію Spring»
Дядько Боб — прізвисько Роберта К. Мартін, відомий програміст і автор. Розробники зазвичай називають його цим ім’ям в розмові. Він також пов’язаний з принципами SOLID, які лежать в основі більшості чистого архітектурного мислення.
“Ти читав промову дядька Боба про кричущу архітектуру? Це повністю змінило те, як я думаю про структуру папок»
** Шестикутна архітектура (порти і адаптери) ** — архітектурний шаблон, представлений Alistair Cockburn. Він описує програму як шістнадцятник з основною бізнес-логікою в центрі. Зовнішній світ спілкується з ядром через порти і адапти. Вона вирішує ту ж саму проблему, що і чиста архітектура, але використовує іншу термінологію. Ви часто побачите, що ці дві назви використовуються взаємозамінно на практиці.
«Ми використовуємо гексагональну архітектуру — домен не знає, чи розмовляє він з реальною базою даних, чи з підробкою в пам’яті. Адаптер справляється з цим»
** Архітектура цибулини ** — тісно пов’ язаний з нею шаблон, описаний Джеффрі Палермо. Як і чиста архітектура, вона використовує концентричні шари з логікою домену в центрі. Назва відображає шарувату, кільцеву структуру. Команди іноді використовують «цибулю», «шестикутну» і «чисту» як грубі синонімії.
«Наша цибулева архітектура зламалася, коли хтось ввімкнув HTTP-клієнтське викликання безпосередньо всередині доменного сервісу. Це саме той вид з’єднання, якому ми намагалися запобігти»
Смуги і межі
** Шлях ** — це кільце або рівень у архітектурі, що групує код за його роллю і відстанню від бізнес- ядра. Чиста архітектура визначає чотири канонічні шари (від найвнутрішнього до зовнішнього): * сутності *, * випадки використання *, * адаптери інтерфейсу *, і * платформи і драйвери *.
«Ця логіка перевірки не повинна знаходитися в шарі контролера — вона належить до шару випадків використання, або в самій сутності»
** Entity ** — найнижчий шар; у ньому містяться загальні для компанії бізнес- правила і об’ єкти домену. Сутності — це прості об’ єкти (або класи), які містять основні дані і поведінку вашого домену. Вони не мають розуміння баз даних, HTTP, або будь-якої структури.
“Суть
Orderзабезпечує правило, що замовлення не може бути розміщено з нульовою кількістю. Це правило живе в сутності, тому що воно справедливе незалежно від того, через який канал прийшов запит»
** Випадок використання ** — другий шар всередині; він містить бізнес- правила, специфічні для програми. Випадок використання оркеструє об’єкти для виконання однієї цілі користувача (наприклад, «замовити», «скасувати підписку»). Приклади використання іноді називають interactors в оригінальному написанні Дядька Боба.
“Я пишу приклад використання для потоку оплати. Він викликає шлюз запасів для перевірки запасів, потім шлюз платежу, а потім зберігає замовлення. Жоден HTTP код не торкається його»
** Адапти інтерфейсу ** — третій шар; він перетворює дані між форматом, який зручно використовувати для випадків використання і об’ єктів, і форматом, який вимагають зовнішні системи (бази даних, веб- платформи, черги повідомлень). Контролери, презентатори і шлюзові мережі живуть тут.
«Шар адаптера інтерфейсу — це місце, де ми серіалізуємо об’єкт домену в відповідь JSON. Сам випадок використання повертає просту структуру даних — презентатор форматує її»
** Frameworks & Drivers ** — зовнішній шар; це місце, де знаходиться весь код, що стосується конкретної платформи і вводу/ виводу: маршрутизація Express або Django, налаштування ORM, клієнти message- broker. Це найнестійкіші частини системи — рамки змінюються, але ваша бізнес-логіка не повинна.
«Ми можемо обміняти нашу базу даних ORM в шарі рамок, не торкаючись жодної лінії бізнес-логіки. Це все, що я маю сказати»
** Архітектурна межа ** — навмисний інтерфейс, який відокремлює один шар від іншого і забезпечує дотримання правила залежності. Перетин межі повинен завжди йти в одному напрямку: всередину. Архітектурні межі часто вимагаються за допомогою інтерфейсів (в статично типованих мовах), так що внутрішній шар залежить від абстракції, а не від конкретної реалізації.
«Ми накреслили жорстку архітектурну межу між шаром use-case і базою даних. Все, що перетинає цю межу, має проходити через інтерфейс»
** Правило залежності ** — фундаментальне обмеження чистої архітектури: * залежності коду повинні бути спрямовані всередину *. Зовнішній шар може залежати від внутрішнього шару, але внутрішній шар ніколи не повинен залежати від зовнішнього шару. Це правило робить основну логіку перевіряною і незалежно розгортається.
“Ваш варіант використання — імпорт класу об’ єкта Hibernate. Це порушує правило залежності — типи персистентності ніколи не повинні витікати в домен»
Порти, адаптери і ролі підтримки
** Порт ** — у гексагональній архітектурі порт є інтерфейсом, який визначає * як * програма спілкується з зовнішнім світом. Існує два види: * первинні порти * (також звані * драйвові порти *) визначають, як зовнішні актори викликають програму (наприклад, контролер HTTP викликає інтерфейс випадка використання). * вторинні порти * (також звані * драйвові порти *) визначають, як програма викликає інфраструктуру (наприклад, інтерфейс сховища для доступу до бази даних).
“Встановіть вторинний порт — інтерфейс сховища — потім напишіть два адаптери: один для Postgres, один для списку в пам’ яті. Ваші тести використовують адаптер пам’яті і ніколи не торкаються бази даних»
** Адаптер ** — конкретна реалізація порту. Адаптер знає, як перекладати між інтерфейсом, що звернений до домену, і певною технологією. HTTP- адаптер перетворює вхідний веб- запит на виклик випадка використання. Адаптер бази даних реалізує інтерфейс сховища за допомогою SQL або ORM.
«Ми написали адаптер Stripe, який реалізує наш
PaymentGatewayпорт. Якщо ми коли-небудь змінимо постачальників платіжних послуг, ми просто напишумо новий адаптер — випадок використання залишиться незмінним»
** Шлюз ** — звичайна назва для вторинного адаптера, який обгортає зовнішню службу або сховище даних. Шлюз * сховища * обробляє доступ до бази даних; шлюз * електронної пошти * обробляє доступ до постачальника електронної пошти. Деякі команди використовують «шлюз» і «адаптер» взаємозамінно в контексті вихідних інтеграцій.
“Шлюз інкапсулює всі SQL-запити. Випадок використання просто викликає
userGateway.findById(id)і повертає об’єкт домену.”
** Presenter ** — адаптер інтерфейсу, відповідальний за форматування виводу випадка використання у модель перегляду (JSON, HTML або будь- який інший формат). Випадок використання викликає презентатор з необробленими вихідними даними; презентатор перетворює їх для механізму доставки. Цей шаблон зберігає логіку форматування поза бізнес- шаром.
“Презентатор перетворює домен типу
Moneyна локалізований рядок валюти. Випадок використання не має уявлення, як буде відображатися сума»
Screaming Architecture — концепція дядька Боба: структура верхнього рівня кодової бази повинна кричати про те, що система (наприклад, «система охорони здоров’я», «управління кредитами»), а не про те, яку фреймворк вона використовує. Якщо відкриваючи проект і бачать тільки controllers/, models/, views/ не говорить вам нічого про бізнес-домен, архітектура не кричить.
“Коли я клонував репозиторій, структура папки просто казала ‘MVC’. Это не сказало мне ничего о бизнесе. Дядько Боб сказав би, що це не кричаща архітектура»
Співвідношення, когерентність і якість дизайну
** Спільна робота ** — ступінь залежності одного модуля від іншого. Висока сполученість означає, що зміна в одному місці змушує зміни в іншому місці. Чиста архітектура активно зменшує з’ єднання між шарами, застосовуючи правило залежності і використовуючи інтерфейси на межах.
«Причиною того, що цей рефакторинг такий болісний, є високе з’єднання між шаром обслуговування і моделями ORM. Нам потрібно ввести маппер, щоб розірвати цю залежність»
** Сплощеність ** — ступінь, у якому елементи всередині модуля належать до одного. Висока сплоченість означає, що клас або модуль виконує одну річ добре; низька сплоченість означає, що він змішує неспоріднені обов’ язки. Хороша архітектура має на меті високу когерентність всередині шарів і низьку зв’язність між ними.
“Ця
UserServiceмає низьку когерентність — вона обробляє автентифікацію, оновлення профілю і повідомлення електронної пошти. Розділимо його на три фокусовані випадки використання»
Як використовувати ці терміни в розмові
Архітектурний словник є найбільш корисним, коли він точний і спільний. Ось деякі природні шаблони для звичайних ситуацій.
В обзоре кода:
«Цей контролер робить занадто багато — він отримує дані, виконує обчислення і форматує відповідь. Обчислення належить до випадку використання, а форматування належить до презентатора»
** Під час сеансу розробки: **
“Перед тим, як ми почнемо кодування, давайте погодимося на наші порти. Нам потрібен первинний порт для виклику REST API, і два вторинних порти — один для бази даних і один для служби попереджень»
** Під час пояснення архітектури новому члену команди: **
“Ми слідуємо гексагональній архітектурі. Модель домену у центрі нічого не знає про Spring або Postgres. Все, що торкається зовнішнього світу, проходить через адаптер, який реалізує один з наших портових інтерфейсів»
** Під час відкидання скорочення: **
“Я знаю, що швидше викликати сховище безпосередньо з сутності, але це порушує правило залежності. Давайте зберемо проблему бази даних в шарі інтерфейс-адаптера»
Швидка реакція
| Term | Layer / Role | Plain English Definition |
|---|---|---|
| Clean Architecture | Architectural style | Concentric layers; business logic at the centre, frameworks on the outside |
| Dependency Rule | Core principle | Dependencies always point inward; inner layers never import outer ones |
| Entity | Innermost layer | Domain object with enterprise-wide business rules |
| Use Case | Second layer | Application-specific logic that orchestrates entities for one user goal |
| Port | Boundary interface | Interface defining how the app communicates with the outside world |
| Adapter | Outer layer | Concrete implementation of a port; translates between domain and technology |
| Gateway | Interface adapter | Adapter wrapping an external data store or service |
| Presenter | Interface adapter | Formats use-case output into a view model for the delivery mechanism |
| Coupling | Design quality | Degree of dependency between modules (lower is better) |
| Cohesion | Design quality | Degree to which a module’s elements belong together (higher is better) |