6. Архітектурні патерни
6. Архітектурні патерни
У попередніх тактичних патернах, розглянутих у цій книзі, описувалися різні способи моделювання та реалізації бізнес-логіки. У цій главі тактичний дизайн розглядатиметься в ширшому контексті, що включає різноманітні способи координації взаємодій і залежностей між компонентами системи.
Співставлення бізнес-логіки та архітектурних патернів
Бізнес-логіка — найважливіша, але далеко не єдина частина програмної системи. Для реалізації функціональних і нефункціональних вимог на кодову базу покладається значно ширший спектр завдань. Це включає:
- Взаємодію з користувачами для отримання вхідних даних.
- Надання вихідних даних.
- Використання механізмів зберігання для фіксації стану.
- Інтеграцію із зовнішніми системами та постачальниками інформації.
Відсутність чіткої організації у вирішенні цих завдань ускладнює внесення змін до коду. При необхідності змін у бізнес-логіці складно одразу визначити, які частини коду потрібно модифікувати. Деякі зміни можуть мати несподіваний вплив на зовсім, здавалося б, не пов'язані частини системи. Так само легко пропустити ті фрагменти коду, які необхідно змінити. Усі ці проблеми значно збільшують витрати на підтримку кодової бази.
Застосування архітектурних патернів вводить організаційні принципи для різних аспектів кодової бази, встановлюючи чіткі межі між ними. Це дозволяє відповісти на запитання, як саме бізнес-логіка пов’язана з введенням, виведенням та іншими інфраструктурними компонентами системи.
Архітектурні патерни впливають на:
- Взаємодію компонентів між собою.
- Спільний доступ до даних.
- Посилання компонентів один на одного.
Три основні архітектурні патерни
Давайте розглянемо три ключові архітектурні патерни додатків:
- Шарова архітектура (Layered Architecture).
- Порти й адаптери (Ports and Adapters).
- CQRS (Command Query Responsibility Segregation).
Шарова архітектура
- Взаємодія з користувачами.
- Реалізація бізнес-логіки.
- Збереження даних.
Структура шарів представлена на рисунку:
- Шар представлення (Presentation Layer).
- Шар бізнес-логіки (Business Logic Layer).
- Шар доступу до даних (Data Access Layer).

Шар представлення (Presentation Layer)
На шарі представлення реалізується користувацький інтерфейс програми для взаємодії з її користувачами. У традиційній формі цей шар включає графічний інтерфейс, наприклад, веб-інтерфейс або настільний застосунок.
У сучасних системах шар представлення охоплює ширший спектр засобів взаємодії:
- Графічний інтерфейс користувача (GUI).
- Інтерфейс командного рядка (CLI).
- API для інтеграції з іншими системами.
- Підписка на події у брокері повідомлень.
- Теми повідомлень для публікації подій.

Шар бізнес-логіки (Business logic layer)
Як видно з назви, цей шар відповідає за реалізацю та інкапсуляцію бізнес-логіки програми. Саме тут реалізуються бізнес-рішення. Як зазначає Ерік Еванс (Eric Evans), цей шар є серцем програмного забезпечення.
На цьому шарі реалізуються патерни бізнес-логіки, описані в розділах 5-7, наприклад, активні записи або модель предметної області (див. рис.).

Шар доступу до даних (Data access layer)
Шар доступу до даних забезпечує доступ до механізмів зберігання інформації.
У первісній формі патерна передбачалася лише база даних системи. Але, як і у випадку з шаром представлення, відповідальність цього шару у сучасних системах стала значно ширшою:
- Багато баз даних. З часів революції NoSQL система зазвичай працює з кількома базами даних. Наприклад, документоорієнтоване сховище може виступати в ролі робочої бази даних, пошуковий індекс — для динамічних запитів, а база даних в оперативній пам'яті — для високопродуктивних операцій.
- Інші засоби зберігання. Традиційні бази даних не є єдиним засобом зберігання інформації. Наприклад, для зберігання файлів може використовуватися хмарне об'єктне сховище, а для зв'язку між функціями програми — шина повідомлень.
- Зовнішні постачальники інформації. Цей шар також включає інтеграцію з API зовнішніх систем або сервісами хмарних провайдерів, такими як переклад, дані фондового ринку, розпізнавання аудіо тощо (див. рис)

Зв'язок між шарами
На рис. 8.5 шар представлення звертається лише до шару бізнес-логіки. Він не знає нічого про дизайн шару доступу до даних.

Варіація: Сервісний шар (Service layer)
Зазвичай патерн шарової архітектури розширюється додатковим сервісним шаром.
Сервісний шар (Service layer)
Визначає межу застосунку через набір сервісів, які встановлюють доступні дії та координують реакцію програми на кожну дію. — "Патерни архітектури корпоративних додатків"
Сервісний шар виступає посередником між існуючими шарами програми: представлення та бізнес-логіки.
Розглянемо наступний код:
namespace MvcApplication.Controllers
public class UserController: Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ContactDetails contactDetails)
{
OperationResult result = null;
try
{
_db.StartTransaction();
var user = new User();
user.SetContactDetails(contactDetails);
user.Save();
_db.Commit();
result = OperationResult.Success;
}
catch (Exception ex)
{
_db.Rollback();
result = OperationResult.Exception(ex);
}
return View(result);
}
}
Показаний у прикладі MVC-контролер належить до шару представлення. Він надає інтерфейс для створення нового користувача. Створення нового екземпляра та його збереження здійснюється через об'єкт активного запису user.
Більше того, цей код керує транзакціями бази даних, щоб у разі помилки повернути правильну відповідь. Як показано на рис., для подальшого відокремлення шару представлення та базової бізнес-логіки, логіку керування транзакціями можна перенести на сервісний шар.

Сервісний шар як логічна межа
Сервісний шар виступає фасадом шару бізнес-логіки: він надає інтерфейс, який відповідає методам публічного інтерфейсу, інкапсулюючи виклики нижчих шарів.
Наприклад:
interface CampaignManagementService {
OperationResult CreateCampaign(CampaignDetails details);
OperationResult Publish(CampaignId id, PublishingSchedule schedule);
OperationResult Deactivate(CampaignId id);
OperationResult AddDisplayLocation(CampaignId id, DisplayLocation newLocation);
}
Усі показані вище методи відповідають публічному інтерфейсу системи. Проте їм не вистачає деталей реалізації, пов'язаних із представленням. Відповідальність шару представлення обмежується наданням необхідних вхідних даних сервісному шару та поверненням його відповідей викликаючій стороні.
Рефакторинг з винесенням логіки оркестрації у сервісний шар
namespace ServiceLayer {
public class UserService {
public OperationResult Create(ContactDetails contactDetails) {
OperationResult result = null;
try {
_db.StartTransaction();
var user = new User();
user.SetContactDetails(contactDetails);
user.Save();
_db.Commit();
result = OperationResult.Success;
} catch (Exception ex) {
_db.Rollback();
result = OperationResult.Exception(ex);
}
return result;
}
}
}
namespace MvcApplication.Controllers {
public class UserController : Controller {
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ContactDetails contactDetails) {
var result = userService.Create(contactDetails);
return View(result);
}
}
}
Переваги явного сервісного шару
Коли сервісний шар не потрібен?
- Якщо бізнес-логіка реалізована у вигляді транзакційного сценарію, то вона сама є сервісним шаром, оскільки вже надає методи для формування публічного інтерфейсу системи. У цьому випадку API сервісного шару просто дублює інтерфейси транзакційних сценаріїв.
Коли сервісний шар необхідний?
- Якщо патерн бізнес-логіки потребує зовнішнього оркестратора, як у випадку з патерном активного запису. У такому випадку на сервісному шарі реалізується патерн транзакційного сценарію, а активні записи, з якими він працює, знаходяться на шарі бізнес-логіки.
Термінологія
- Шар представлення = шар користувацького інтерфейсу.
- Сервісний шар = прикладний шар.
- Шар бізнес-логіки = шар предметної області = шар моделі.
- Шар доступу до даних = шар інфраструктури.
Щоб уникнути плутанини, патерн тут представлено з використанням початкової термінології. Водночас я схиляюся до таких понять, як «шар користувацького інтерфейсу» та «шар інфраструктури», оскільки ці терміни краще відображають обов’язки сучасних систем та шарів додатків, не допускаючи плутанини з фізичними межами сервісів.
Коли краще використовувати шарову архітектуру
Завдяки наявності у цьому архітектурному патерні залежності між шарами бізнес-логіки та доступу до даних, він добре підходить для систем, бізнес-логіка яких реалізована з використанням транзакційного сценарію або патерна активного запису.
Реалізувати модель предметної області в шаровій архітектурі все ж можна, але патерн, який розглядається далі, підходить для цього набагато краще.
Додатково: порівняння шарів і рівнів
- Шар — це логічна межа.
- Рівень — це фізична межа.
Усі шари в шаровій архітектурі мають один і той самий життєвий цикл: вони реалізуються, розвиваються й розгортаються як єдине ціле. А рівень — це незалежно розгортований сервіс, сервер або система.

Розглянемо, наприклад, систему з архітектурою N-Tier:
- Браузер, який працює на настільному комп’ютері або мобільному пристрої.
- Зворотний проксі-сервер, який перенаправляє запити до веб-додатку.
- Веб-додаток, який працює на веб-сервері й взаємодіє із сервером бази даних.
Усі ці компоненти можуть працювати на одному фізичному сервері або бути розподілені між кількома серверами. Однак, оскільки кожен компонент може розгортатися й управлятися незалежно від інших, це рівні, а не шари.
Шари всередині веб-додатку є логічними межами.
Порти й адаптери (Ports and Adapters)
Архітектура портів і адаптерів усуває недоліки шарової архітектури й краще підходить для реалізації складнішої бізнес-логіки.
Цікаво, що обидва патерни дуже схожі. Рефакторимо шарову архітектуру в архітектуру портів і адаптерів.
Термінологія

Принцип інверсії залежностей (Dependency Inversion Principle)
Щоб відповідати принципу DIP, давайте, як показано на рис, розвернемо залежності у зворотному напрямку.

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

Архітектура, зображена на рис, представляє архітектурний патерн портів і адаптерів (Ports and Adapters). Шар бізнес-логіки не залежить ні від одного з нижніх шарів, що дозволяє реалізувати патерни моделі предметної області та моделі предметної області, заснованої на подіях (Event Sourced Domain Model).
Чому цей патерн називається порти й адаптери?
Щоб відповісти на це питання, розглянемо, як інфраструктурні компоненти інтегровані з бізнес-логікою.
Інтеграція інфраструктурних компонентів
Основна мета архітектури портів і адаптерів — відокремити бізнес-логіку системи від її інфраструктурних компонентів.
Замість того, щоб посилатися на інфраструктурні компоненти або викликати їх напряму, шар бізнес-логіки визначає «порти», які мають бути реалізовані на шарі інфраструктури. А на шарі інфраструктури реалізуються «адаптери» — конкретні реалізації інтерфейсів портів для роботи з різними технологіями (див. рис).

Абстрактні порти стають конкретними адаптерами на шарі інфраструктури або шляхом впровадження залежностей, або шляхом налаштування під час початкового завантаження.
Наприклад, ось як виглядає можливе визначення порту та конкретний адаптер для шини повідомлень:
namespace App.BusinessLogicLayer
{
public interface IMessaging
{
void Publish(Message payload);
void Subscribe(Message type, Action callback);
}
}
namespace App.Infrastructure.Adapters
{
public class SQSBus: IMessaging {
// ... implementation details
}
}
Варіанти
І все ж ці патерни можна помилково вважати концептуально різними. Це ще один приклад важливості єдиної мови.
Коли доцільніше використовувати порти й адаптери
Розділення відповідальності команд і запитів
(Command-Query Responsibility Segregation)
Давайте розглянемо, навіщо може знадобитися таке рішення і як його реалізувати.
Мультипарадигмальне моделювання
(Polyglot modelling)
Використання єдиної моделі для всіх потреб системи може бути ускладненим або навіть неможливим. Наприклад, як уже згадувалося у главі 7, обробка транзакцій (online transaction processing – OLTP) та аналітична обробка (online analytical processing – OLAP) можуть вимагати різних представлень даних.
І, нарешті, що потрібно згадати – патерн CQRS тісно пов’язаний із джерелом подій (event sourcing). Спочатку CQRS був визначений для подолання обмежень у виконанні запитів при використанні моделі, заснованої на подіях (event sourced domain model): за один раз можна було запитувати події лише одного екземпляра агрегату.
Застосування патерна CQRS дозволяє використовувати одразу кілька механізмів збереження для представлення різних моделей даних розроблюваної системи.
Реалізація
Як випливає із назви, патерн розділяє обов’язки моделей системи. Існують два типи моделей: модель виконання команд і моделі читання.
Модель виконання команд
Модель виконання команд також є єдиною моделлю, що представляє строго узгоджені дані – джерелом істини (source of truth).
Повинна бути надана можливість зчитування строго узгодженого стану бізнес-об’єкта і підтримка оптимістичного управління конкурентним доступом для оновлення цих об’єктів.
Моделі читання (проєкції)
Система може визначити стільки моделей, скільки потрібно для надання даних користувачам або інформації іншим системам.
І, нарешті, моделі читання доступні тільки для читання. Жодна із системних операцій не може безпосередньо змінювати дані моделей читання.
Проєктування моделей читання
Щоб моделі читання запрацювали, система повинна проєктувати зміни із моделі виконання команд.

Проекція моделей читання аналогічна поняттю матеріалізованого представлення (materialized view) у реляційних базах даних: при кожному оновленні вихідної таблиці зміни мають відображатися в попередньо кешованих представленнях.
А тепер давайте розглянемо два способи створення проєкцій: синхронний та асинхронний.
Синхронні проєкції
При синхронному оновленні проєкцій оновлення даних відбувається за моделлю наздоганяючої підписки (catch-up subscription):
- Механізм проєктування запитує в OLTP-базі нові або оновлені записи після останньої контрольної точки (checkpoint).
- Механізм проєктування використовує оновлені дані для створення або оновлення моделей читання.
- Механізм проєктування зберігає контрольну точку останнього обробленого запису.
Цей процес показаний на рис. у вигляді діаграми послідовності.

Щоб наздоганяюча підписка працювала, модель виконання команд повинна встановлювати контрольну точку (checkpoint) для всіх нових або оновлених записів бази даних. Механізм зберігання також має підтримувати запит записів на основі контрольних точок.
Контрольна точка може бути реалізована за допомогою можливостей баз даних. Наприклад, як показано на таблиці. , для генерації унікальних інкрементальних чисел під час вставки або оновлення рядка у SQL Server може використовуватися стовпець «rowversion».
| Id | Ім'я | Прізвище | Checkpoint |
|---|---|---|---|
| 1 | Том | Кук | 0x0000000000001792 |
| 2 | Гарольд | Еліот | 0x0000000000001793 |
| 3 | Діана | Деніелс | 0x0000000000001796 |
| 4 | Діана | Деніелс | 0x0000000000001795 |
Асинхронні проєкції
У сценарії асинхронного проєктування модель виконання команд публікує всі зафіксовані зміни в шину повідомлень. Як показано на рис, механізми проєктування системи можуть підписуватися на опубліковані повідомлення і використовувати їх для оновлення моделей читання.

Тому завжди рекомендується реалізовувати синхронну проєкцію, а в разі необхідності надбудовувати над нею додаткову асинхронну проєкцію.
Розподіл моделей
В архітектурі CQRS відповідальності моделей розділені відповідно до їх типу. Команда може працювати лише в строго узгодженій моделі виконання команд. Запит не може безпосередньо змінювати збережений стан системи – ні моделі читання, ні моделі виконання команд.
Коли краще використовувати CQRS
З точки зору інфраструктури, CQRS дозволяє використовувати можливості різних типів баз даних: наприклад, для збереження моделі виконання команд використовувати реляційні бази даних, для повнотекстового пошуку – індекс пошуку, а для швидкого витягування даних – заздалегідь оброблені файли.
Крім того, CQRS природно підходить для моделей предметної області, заснованих на подіях (event sourced domain model).
Сфера застосування
Наприклад, в обмеженому контексті, який охоплює кілька піддоменів, піддомени можуть бути різних типів: основні (core), допоміжні (supporting) чи універсальні (generic).

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

Висновок
::field-group
items:
- title: Багатошарова архітектура description: Передбачає поділ кодової бази на основі технологічних завдань. Добре підходить для систем, які використовують активні записи.
- title: Архітектура портів і адаптерів description: Бізнес-логіка стає центральною і відділяється від усіх інфраструктурних залежностей. Ідеально для моделі предметної області.
- title: Патерн CQRS description: Представляє одні й ті самі дані одразу в декількох моделях. Обов’язковий для event-sourced систем, але корисний усюди, де потрібні різні проєкції.
::
Патерни, які будуть розглянуті у наступному розділі, призначені для вирішення архітектурних завдань із акцентом на реалізацію надійної взаємодії між різними компонентами системи.
Вправи
- Які з розглянутих архітектурних патернів можна використовувати з бізнес-логікою, реалізованою у вигляді патерна активного запису?
- А) Багатошарова архітектура. - Б) Порти і адаптери. - В) CQRS. - Г) А і Б.
- Який із розглянутих архітектурних патернів відокремлює бізнес-логіку від інфраструктурних задач?
- А) Багатошарова архітектура. - Б) Порти і адаптери. - В) CQRS. - Г) Б і В.
- Припустимо, що при реалізації патерна портів і адаптерів необхідно інтегрувати шину повідомлень хмарного провайдера. На якому шарі повинна бути реалізована інтеграція?
- А) На шарі бізнес-логіки. - Б) На прикладному шарі. - В) На шарі інфраструктури. - Г) На будь-якому шарі.
- Яке з наведених тверджень стосовно патерна CQRS є правильним?
- А) Асинхронні проекції легше масштабувати. - Б) Можна використовувати або синхронну, або асинхронну проекцію, але не обидві одночасно. - В) Команда не повертає ніякої інформації стороні, що викликає. Для отримання результатів виконаних дій код, що викликає, завжди повинен використовувати моделі читання. - Г) Команда може повертати інформацію, якщо вона виходить зі строго узгодженої моделі. - Д) А і Г.
- Патерн CQRS дозволяє представляти одні й ті самі бізнес-об’єкти у декількох моделях збереження інформації, дозволяючи таким чином в одному й тому ж обмеженому контексті працювати з декількома моделями. Чи суперечить це уявленню про те, що обмежений контекст є межею моделі?