У попередній матеріал про момент, коли звичайного CRUD уже недостатньо я писала про момент, коли даних уже недостатньо просто мати у таблиці, бо навколо них з’являються правила і стани.
Наступне питання складніше: де закінчується одна частина предметної області і починається інша?
Під час роботи над AVN Center я кілька разів бачила, як початкова межа виглядала логічною, а потім система росла і показувала, що всередині насправді живуть дві різні відповідальності.
Це нормально. Я більше не очікую, що правильну карту системи можна намалювати один раз на початку і більше ніколи не змінювати.
На початку я не намагаюся вгадати всі межі
At the beginning: Product | +-- name +-- code +-- status +-- settings +-- access information +-- registration informationКоли функцій мало, об’єднання часто не створює проблем.
Усе стосується одного продукту, використовує один ідентифікатор і може навіть зберігатися поруч.
Я не розділяю таку модель лише тому, що в DDD повинно бути багато контекстів.
Спочатку простіше дозволити предметній області показати свої реальні правила.
Перший сигнал для мене - однакові слова починають означати різне
«active» В одному випадку:продукт доступний для пропонування В іншому випадку:користувач наразі має доступ Одне й те саме слово.Різні значення.Різні правила.Мова дуже добре показує межі.
Якщо команда говорить "активний", але в одному місці має на увазі стан самого продукту, а в іншому - право конкретного користувача ним користуватися, це вже не зовсім одне поняття.
Поки система маленька, різницю легко тримати в голові. Коли сценарії ростуть, однакове слово починає створювати неправильні припущення.
У цей момент я питаю: чи не намагаємося ми однією моделлю описати дві різні речі?
Другий сигнал - частини змінюються з різних причин
Опис продукту змінюється через:- зміну метаданих- зміну життєвого циклу продукту- зміну версії продукту Правила доступу змінюються через:- зміну політики- зміну стану користувача- зміну часу або прав доступуДля мене це один із найсильніших практичних критеріїв.
Якщо дві групи правил змінюються через різні події, різні вимоги і різні рішення, їхній спільний клас або сервіс поступово стає місцем випадкової зв’язаності.
Вони можуть використовувати той самий ідентифікатор, але це ще не означає, що вони повинні належати до однієї моделі.
Третій сигнал - різний життєвий цикл
Річ A: draft -> active -> archived Річ Б: pending -> granted -> expired -> revokedДві речі можуть бути тісно пов’язані, але жити за різними правилами часу.
Одна може існувати роками і рідко змінюватися. Інша може створюватися і завершуватися багато разів навколо неї.
Якщо для них потрібні різні стани, різні переходи і різні правила завершення, це сильний аргумент не змушувати одну сутність керувати обома життєвими циклами.
Четвертий сигнал - різні правила, які не можна порушити
Контекст A гарантує: «Кожен активний продуктмає коректні дані про продукт». Контекст B гарантує: «Доступ надається лишеза умови виконаннявласних умов».Межа стає помітнішою, якщо запитати не "які таблиці пов’язані?", а "які правила мають бути правдивими завжди?".
Якщо дві частини системи захищають різні набори правил і для зміни однієї не потрібно одночасно змінювати іншу, я схиляюся до окремих контекстів.
Foreign key не визначає межу предметної області
products.id ^ |access_records.product_id Зв'язок у базі даних:yes Та сама модель предметної області:не обов’язковоЦе була важлива зміна в моєму мисленні.
Раніше близькість таблиць легко підштовхувала мене тримати логіку разом.
Тепер я сприймаю зовнішній ключ як технічний зв’язок між даними, а не як доказ того, що вся поведінка повинна жити в одному домені.
Спільний сценарій ще не означає спільний контекст
Іноді одна прикладна операція працює одразу з кількома частинами системи.
Це ще не означає, що їх потрібно об’єднати в одну велику модель.
Спільний сценарій може координувати дві чіткі моделі, якщо кожна з них має власні правила і зрозумілу відповідальність.
Для мене важливіше, хто володіє правилом, ніж те, хто випадково бере участь в одному запиті.
Простий приклад: опис продукту і право ним користуватися
Product відповідає на запитання:«Що це за продукт?» Доступ відповідає на запитання:«Чи може цей суб’єкт скористатися ним зараз?» Вони пов’язані,але відповідають на різні запитання.Цей приклад близький до досвіду, який я отримала під час розвитку AVN Center, але навмисно спрощений.
Опис продукту відповідає на питання, що це за продукт, які в нього властивості і який у нього власний стан.
Право користування відповідає на інше питання: чи може певний суб’єкт користуватися цим продуктом зараз.
Між ними є зв’язок. Але причина зміни опису продукту і причина зміни права користування зовсім різні.
Саме такі відмінності поступово показують мені реальну межу.
Я не розділяю модель при першій ознаці різниці
Надто раннє розділення теж має ціну.
З’являються окремі інтерфейси, перетворення даних, додаткові залежності і координація між частинами.
Якщо в системі ще немає реальної незалежності правил, таке розділення може створити більше роботи, ніж користі.
Тому я чекаю повторюваних сигналів: різна мова, різні причини змін, різний життєвий цикл, різні інваріанти.
Я розділяю тоді, коли спільна модель починає заважати
Я розглядаю варіант розділення, коли: - один клас знає про непов'язані між собою правила- один статус має різні значення- зміни в одній функції порушують роботу іншої- для тестів потрібна непов'язана між собою підготовка- назви стають неоднозначними- один життєвий цикл очікує на іншийДля мене найважливіший сигнал - не розмір файла і не кількість методів.
Це момент, коли одна модель змушує різні правила знати одне про одного без реальної потреби.
Тоді розділення вже не є архітектурою заради архітектури. Воно прибирає зайву залежність і робить кожну частину простішою.
Межа, яку я вибрала сьогодні, не є назавжди
Це, мабуть, найбільше полегшення, яке я отримала з практики DDD.
Не потрібно довести архітектуру до абсолютної правильності до першого рядка коду.
Потрібно зробити межі достатньо хорошими для знань, які є сьогодні, і залишити можливість уточнити їх, коли предметна область покаже більше.
Розвиток моделі не означає, що початкова архітектура була поганою. Часто це просто означає, що команда дізналася про продукт більше.
Коли межа змінюється, я переношу правила, а не просто файли
До: Одна модель +-- правила щодо продукту +-- правила доступу Після: Контекст продукту +-- правила щодо продукту Контекст доступу +-- правила доступу Явний зв'язокміж нимиЯкщо я просто перемістила класи у дві папки, але один сервіс і далі знає всі правила обох частин, межа фактично не змінилася.
Розділення стає реальним, коли кожна сторона сама володіє своєю мовою, правилами і станами, а зв’язок між ними стає явним.
Тести часто показують неправильну межу раніше за діаграму
Є дуже практичний сигнал: щоб перевірити одне правило, тесту доводиться підготувати багато стану, який до цього правила начебто не має стосунку.
Якщо це повторюється, я перевіряю, чи не змішалися дві відповідальності.
Хороша межа часто робить тести локальнішими: для перевірки правила потрібні тільки дані цієї частини предметної області.
У AVN Center я прийшла до цього не з діаграми, а з повторюваних правил
Зв’язок із AVN Center тут для мене важливий саме як досвід, а не як опис внутрішньої архітектури.
Межі ставали зрозумілішими тоді, коли я бачила повторювані правила, різні причини змін і різні життєві цикли.
Окремий практичний приклад DDD з роботи над Center показує один із практичних кроків цієї роботи, але загальний урок ширший: межі краще виростають із поведінки системи, ніж із назв майбутніх модулів.
Питання, які я ставлю перед розділенням домену
Перед тим як створювати окрему межу, я питаю:
- Чи однакові слова тут мають однаковий зміст?
- Чи ці правила змінюються з одних і тих самих причин?
- Чи в об’єктів справді спільний життєвий цикл?
- Чи вони захищають один набір інваріантів?
- Чи зміна одного майже завжди вимагає одночасної зміни іншого?
- Чи спільна модель спрощує код, а не лише зменшує кількість класів?
- Чи тести однієї частини потребують багато чужого стану?
- Чи зможу я пояснити відповідальність кожної частини одним простим реченням?
Хороша межа стає видимою з часом
Раніше мені хотілося знайти правильну межу одразу.
Тепер я дивлюся на це інакше: межа є робочою гіпотезою про те, які правила належать разом.
Якщо мова, життєвий цикл і причини змін залишаються спільними, межа працює.
Якщо вони починають розходитися, модель дає сигнал, що її час уточнити.
Для мене DDD у системі, що росте, не про те, щоб передбачити майбутнє. Воно про те, щоб помічати, коли предметна область вже показала нову межу.
Я не намагаюся намалювати ідеальні межі наперед. Я стежу за мовою, правилами і причинами змін, а система поступово показує, де межа проходить насправді.



