Я адаптую CQRS 1 бідолахи досить довгий час, тому що люблю його гнучкість мати детальні дані в одному сховищі даних, надаючи великі можливості для аналізу і, таким чином, збільшуючи ділову цінність, і коли потрібно, для іншого читання, що містить денормалізовані дані для підвищення продуктивності .
Але, на жаль, з самого початку я боровся з проблемою, куди саме я повинен розміщувати бізнес-логіку в цьому архітектурі.
З того, що я розумію, команда - це засіб спілкування намірів і не має зв’язків із доменом сама по собі. Вони в основному є даними (німі - якщо хочете) передавати об’єкти. Це дозволяє командам легко переноситися між різними технологіями. Те саме стосується подій, як відповіді на успішно завершені події.
У типовому додатку DDD бізнес-логіка знаходиться в межах сутності, ціннісних об'єктів, сукупних коренів, вони багаті як даними, так і поведінкою. Але команда не є об'єктом домену, тому вона не повинна обмежуватися представленням даних домену, тому що це надто сильно напружує їх.
Тож справжнє питання: Де саме логіка?
Я виявив, що я, як правило, стикаюся з цією боротьбою найчастіше, коли намагаюся побудувати досить складний сукупність, який встановлює деякі правила щодо комбінацій його значень. Крім того , при моделюванні об'єктів домену Мені подобається стежити за відмовостійкість швидко парадигмою, знаючи , коли об'єкт досягає методу це в допустимому стані.
Скажімо, агрегат Car
використовує два компоненти:
Transmission
,Engine
.
Обидва Transmission
і Engine
цінні об'єкти представлені як супер типи і мають відповідно до підтипів, Automatic
і Manual
передач, Petrol
і Electric
двигунів відповідно.
У цій області живе на своєму власному і успішно створена Transmission
, будь то Automatic
чи Manual
, або будь-який тип Engine
цілком нормально. Але Car
сукупність вводить кілька нових правил, застосовних лише тоді, коли Transmission
і Engine
об’єкти використовуються в одному контексті. А саме:
- Коли автомобіль використовує
Electric
двигун, єдиний дозволений тип передачі - цеAutomatic
. - Коли автомобіль використовує
Petrol
двигун, він може мати будь-який типTransmission
.
Я міг би виявити порушення цього поєднання компонентів на рівні створення команди, але, як я вже говорив раніше, з того, що я розумію, цього робити не слід, оскільки команда тоді міститиме ділову логіку, яка повинна бути обмежена доменним шаром.
Один із варіантів - перенести цю перевірку ділової логіки на саму валідатор команд, але це, мабуть, не вірно. Схоже, я би деконструював команду, перевіряв її властивості, отримані за допомогою getters та порівнював їх у валідаторі та перевіряв результати. Це кричить як порушення закону Деметра для мене.
Відмовившись від згаданого варіанту перевірки, оскільки він не здається життєздатним, здається, що слід використовувати команду та побудувати з неї сукупність. Але де повинна існувати ця логіка? Чи повинен він знаходитися в обробниках команд, відповідальних за обробку конкретної команди? Або це може бути в межах валідатора команд (мені не подобається такий підхід)?
Наразі я використовую команду і створюю з неї сукупність в межах відповідального обробника команд. Але коли я це роблю, якщо у мене є валідатор команд, він би взагалі нічого не містив, тому що, якщо CreateCar
команда існує, вона містила б компоненти, які, на мою думку, є дійсними в окремих випадках, але сукупність могла б сказати інше.
Давайте уявимо собі інший сценарій, що змішує різні процеси перевірки - створення нового користувача за допомогою CreateUser
команди.
Команда містить Id
користувачів, які будуть створені та їх Email
.
Система встановлює такі правила для електронної адреси користувача:
- повинні бути унікальними,
- не повинно бути порожнім,
- має містити не більше 100 символів (максимальна довжина колони db).
У цьому випадку, хоча наявність унікальної електронної пошти є діловим правилом, перевірка її у сукупності має дуже мало сенсу, тому що мені потрібно буде завантажити весь набір поточних електронних листів у систему на пам'ять і перевірити електронну пошту в команді проти сукупності ( Eeeek! Щось, щось, виконання.). Через це я перенесу цю перевірку на валідатор команд, який буде вважатися UserRepository
залежністю та використовувати репозиторій, щоб перевірити, чи є вже користувач з електронною поштою, присутній у команді.
Якщо мова йде про це, раптом має сенс помістити і два інших правила електронної пошти у валідатор команд. Але я маю відчуття, що правила повинні бути дійсно присутніми в User
агрегаті, і що валідатор команд повинен перевіряти лише унікальність, і якщо перевірка успішна, я повинен продовжувати створювати User
агрегат в CreateUserCommandHandler
і передавати його в сховище, яке потрібно зберегти.
Я відчуваю це так, тому що метод збереження сховища, ймовірно, приймає сукупність, яка гарантує, що після передачі сукупності всі інваріанти будуть виконані. Коли логіка (наприклад, порожнеча) присутня лише в рамках самої перевірки команди, інший програміст може повністю пропустити цю перевірку і викликати метод збереження UserRepository
в User
об'єкті безпосередньо, що може призвести до фатальної помилки бази даних, оскільки електронна пошта може мати були занадто довго.
Як ви особисто впораєтеся з цими складними перевірками та перетвореннями? Я в основному задоволений своїм рішенням, але відчуваю, що мені потрібно підтвердити, що мої ідеї та підходи не зовсім дурні, щоб бути дуже задоволеним вибором. Я цілком відкритий для зовсім інших підходів. Якщо у вас є щось, що ви особисто намагалися і дуже добре працювали для вас, я хотів би побачити ваше рішення.
1 Працюючи розробником PHP, відповідальним за створення систем RESTful, моя інтерпретація CQRS трохи відхиляється від стандартного підходу до обробки асинхронно-командної системи , наприклад, іноді повертає результати команд через необхідність синхронної обробки команд.
CommandDispatcher
.