Чи зазвичай ізоляція моделі домену / стійкості є такою незручною?


13

Я занурився в концепцію дизайну, керованого доменом (DDD), і виявив деякі принципи дивними, особливо стосовно ізоляції доменної та стійкої моделі. Ось моє основне розуміння:

  1. Служба на рівні додатків (надає набір функцій) запитує доменні об’єкти із сховища, яке йому потрібно для виконання своєї функції.
  2. Конкретна реалізація цього сховища отримує дані з сховища, для якого воно було реалізовано
  3. Служба повідомляє об'єкту домену, який інкапсулює ділову логіку, виконувати певні завдання, які змінюють його стан.
  4. Служба повідомляє сховищу зберігати модифікований об’єкт домену.
  5. Репозиторію потрібно віднести об’єкт домену назад до відповідного представлення у сховищі.

Потік ілюстрації

Тепер, з огляду на вищенаведені припущення, наступне здається незручним:

Оголошення 2 .:

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

Враховуючи цю проблему, "правильний" спосіб завантаження об'єктів домену, здається, має виділену функцію завантаження для кожного випадку використання. Ці виділені функції потім завантажуватимуть лише ті дані, які вимагаються випадком використання, для якого вони були розроблені. Ось де грає незграбність: По-перше, я повинен був би підтримувати значну кількість функцій завантаження для кожної реалізації сховища, і доменні об’єкти опиняться в неповних станах, що перебувають nullу своїх полях. Останнє технічно не повинно бути проблемою, оскільки якщо значення не було завантажене, воно не повинно вимагати функціоналів, які його вимагали в будь-якому випадку. І все-таки це незручно та потенційно небезпечно.

Об'ява 3 .:

Як доменний об’єкт перевірить обмеження унікальності при побудові, якщо він не має уявлення про сховище? Наприклад, якби я хотів створити новий Userз унікальним номером соціального страхування (який вказаний), найдавніший конфлікт виникне при проханні сховища зберегти об'єкт, тільки якщо в базі даних є обмеження унікальності. В іншому випадку я можу шукати Userвідповідне соціальне забезпечення та повідомити про помилку, якщо воно існує, перш ніж створити нове. Але тоді перевірки обмежень житимуть у службі, а не в об’єкті домену, де вони належать. Я щойно зрозумів, що об’єктам домену дуже добре дозволено використовувати (вводити) сховища для перевірки.

Оголошення 5 .:

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

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

Що стосується баз даних NoSQL, для яких навряд чи існують поняття, подібні до ORM, як ви відстежуєте, які властивості змінювались у моделях домену save()?

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

В загальному:

  • Куди піде транзакційна логіка? Це, безумовно, специфіка стійкості. Деякі інфраструктури зберігання можуть взагалі не підтримувати транзакції (як сховища макетів в пам'яті).
  • Чи потрібно для об'ємних операцій, що змінюють декілька об'єктів, завантажувати, змінювати та зберігати кожен об'єкт окремо для того, щоб перейти через інкапсульовану логіку перевірки об'єкта? Це протилежне виконанню одного запиту безпосередньо на базі даних.

Буду вдячний за певні роз’яснення на цю тему. Чи правильні мої припущення? Якщо ні, то який правильний спосіб вирішити ці проблеми?


1
Хороші моменти та питання, мене також цікавлять. Одна сторона зауваження - якщо ви правильно моделюєте агрегат, це означає, що в будь-який момент існування сукупність екземпляра повинна бути у дійсному стані - це головна точка агрегату (а не використання агрегату як контейнера композиції). Це також означає, що для відновлення сукупної форми даних БД, сам сховище зазвичай повинен буде використовувати конкретний конструктор і набір операцій з мутацією, і я не бачу, як будь-який ОРМ міг автоматично магічно знати, як робити ці операції .
Душан

2
Що ще більше розчаровує, що такі питання, як ваше, задаються навколо досить часто, але, наскільки мені відомо, є приклади ZERO щодо реалізації агрегатів та сховищ, які є за книгою
Душан

Відповіді:


5

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

Читаючи між рядками, здається, ви походите з більш орієнтованого на базу даних активного стилю запису програмування? Для того, щоб дійти до робочої реалізації, я б сказав, що вам потрібно

1: Об'єкти домену не повинні включати весь графік об'єктів. Наприклад, я міг би:

public class Customer
{
    public string AddressId {get;set;}
    public string Name {get;set;}
}

public class Address
{
    public string Id {get;set;}
    public string HouseNumber {get;set;
}

Адреса та Клієнт повинні бути лише частиною одного сукупності, якщо у вас є певна логіка, наприклад, "ім'я клієнта може починатися лише з тієї самої літери, що і доменне ім'я". Ви маєте рацію уникати лінивого завантаження та "Lite" версій об'єктів.

2: Обмеження унікальності, як правило, є репозиторієм, а не об'єктом домену. Не вводите сховища в об’єкти домену, це повернення до активної записи, просто помилка, коли служба намагається зберегти.

Правило бізнесу не є "Немає двох примірників користувача з однаковим SocialSecurityNumber одночасно."

Це те, що вони не можуть існувати в одному сховищі.

3: Писати сховища не важко, ніж окремі методи оновлення властивостей. Насправді ви виявите, що у вас є в основному однаковий код в будь-якому випадку. Це саме той клас, в який ви його поклали.

ORM в наші дні прості і не мають додаткових обмежень у вашому коді. Сказавши це, особисто я вважаю за краще просто рукою закрутити SQL. Це не так складно, ви ніколи не стикаєтеся з будь-якими проблемами з функціями ORM і можете оптимізувати, де потрібно.

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

Загальні питання

  1. Логіка транзакцій йде у сховище. Але у вас нічого не повинно бути, якщо щось таке. Впевнені, що вам знадобляться, якщо у вас є дочірні таблиці, в які ви ставите дочірні об’єкти сукупності, але вони будуть повністю інкапсульовані в методі сховища SaveMyObject.

  2. Масові оновлення. Так, вам слід індивідуально змінити кожен об’єкт, а потім просто додати метод збереження MyMoObjects (Список об'єктів) до вашого сховища, щоб зробити масове оновлення.

    Ви хочете, щоб об’єкт домену або служба домену містили логіку. Не база даних. Це означає, що ви не можете просто "оновити ім'я клієнтського набору = x де y", тому що, коли ви знаєте об'єкт "Клієнт", або CustomerUpdateService робить 20 інших випадків, коли ви змінюєте ім'я.


Чудова відповідь. Ви абсолютно праві, я звик до активного стилю запису кодування, тому шаблон сховища здається дивним на перший погляд. Однак чи не суперечать "худі" доменні об'єкти ( AddressIdзамість Address) принципам ОО?
Подвійний М

Ні, у вас все ще є об’єкт Адреса, його просто не дитина Замовник
Еван

Зображення об’єкта реклами без відстеження змін softwareengineering.stackexchange.com/questions/380274/…
Double M

2

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

Пункт 2 .: (завантаження повних графіків об'єкта)

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

Як ви сказали, очевидним рішенням є використання методів стійкості для кожного випадку використання. Але якщо ви все ще використовуєте ORM для цього, ORM змусить вас упакувати все в об'єкти даних. Що крім того, що насправді не є об'єктно-орієнтованим, знову ж таки не найкращий дизайн для деяких випадків використання.

Що робити, якщо я просто хочу оновити деякі записи? Навіщо мені потрібно представлення об'єкта для всіх записів? І т.д.

Тож рішенням цього є лише не використовувати ORM для випадків використання, для яких це не дуже добре. Реалізуйте випадок використання "природно" таким, який він є, що іноді не вимагає додаткової "абстракції" самих даних (даних-об'єктів), ані абстракції над "таблицями" (сховищами).

Надання наполовину заповнених об'єктів даних або заміна посилань на об'єкти на "ідентифікатори" - це в кращому випадку обходи, а не хороші конструкції, як ви вказали.

Пункт 3 .: (перевірка обмежень)

Якщо наполегливість не буде усунена, кожен випадок використання може, очевидно, легко перевірити будь-яке обмеження. Вимога про те, що об'єкти не знають "сховище", є абсолютно штучною і не є проблемою технології.

Пункт 5. (ORM)

Безумовно, це важлива передумова від'єднання конкретної реалізації зберігання від доменного коду. Однак чи дійсно це стоїть з такою високою ціною?

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

Загальне питання 1 .: (угоди)

Я не думаю, що існує єдине рішення. Якщо ваш дизайн об'єктно-орієнтований, для кожного випадку використання буде "топ" метод. Угода повинна бути там.

Будь-яке інше обмеження є абсолютно штучним.

Загальне питання 2 .: (масові операції)

З ORM ви (для більшості ORM, яких я знаю) змушені пройти окремі об'єкти. Це абсолютно непотрібно і, ймовірно, не буде вашим дизайном, якби ваша рука не була пов'язана з ОРМ.

Вимога відокремлювати "логіку" від SQL походить від ОРМ. Вони є це сказати, оскільки вони не можуть цього підтримати. Це не властиво "погано".

Підсумок

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

Так само не завжди найкраща абстракція DDD-об'єктів-сховищ DDD. Я б навіть зайшов так далеко, щоб сказати, вони рідко є оптимальним дизайном.

Це не залишає для нас рішення, яке не відповідає всім розмірам, тому нам доведеться думати про рішення для кожного конкретного випадку використання, що не є хорошою новиною і очевидно ускладнює нашу роботу :)


Дуже цікаві моменти, дякую за підтвердження моїх припущень. Ви сказали, що існує багато інших способів наполегливості. Чи можете ви порекомендувати впорядковану модель дизайну використовувати для баз даних графіків (без ORM), які все-таки забезпечуватимуть PI?
Подвійний М

1
Я б насправді сумнівався, чи потрібна вам ізоляція (і яка саме) в першу чергу. Ізоляція за технологією (наприклад, база даних, користувальницький інтерфейс тощо) майже автоматично приносить "незграбність", якої ви намагаєтеся уникнути, для переваги дещо простішої заміни технології бази даних. Однак вартість є більш важкою зміною бізнес-логіки, оскільки вона поширюється по шарах. Або ви можете поділитись на бізнес-функції, що ускладнить зміну баз даних, але змінити логіку простіше. Якого ви дійсно хочете?
Роберт Брутігам

1
Ви можете досягти найкращої продуктивності, якщо просто моделювати домен (тобто бізнес-функції) і не абстрагувати базу даних (будь-які реляційні чи графічні значення не мають). Оскільки база даних не абстрагована від випадку використання, той може використовувати найоптимальніші запити / оновлення, які вона хоче, і не потрібно проходити якусь незручну модель-об'єкт, щоб досягти того, що вона хоче.
Роберт Брутігам

Ну, головна мета - утримати проблему наполегливості подалі від ділової логіки, щоб мати чистий код, який легко зрозуміти, розширити та перевірити. Можливість обмінятись технологіями БД - лише бонус. Я бачу, що очевидно, що між ефективністю та незнанням існує тертя, яке, здається, посилюється з графічними БД через потужні запити, які ви можете (але заборонено використовувати).
Подвійний М

1
Як розробник Java Enterprise, я можу вам сказати, що ми намагалися відокремити наполегливість від логіки протягом останніх двох десятиліть. Це не працює. По-перше, розлуки реально ніколи не було досягнуто. Навіть сьогодні є всілякі речі, пов’язані з базою даних, у нібито "ділових" об'єктах, головним з яких є ідентифікатор бази даних (і багато анотацій до бази даних). По-друге, як ви вже говорили, іноді бізнес-логіка виконується в базі даних будь-яким способом. По-третє, саме тому ми маємо конкретні бази даних, щоб мати можливість вивантажити певну логіку, найкраще виконану там, де є дані.
Роберт Брутігам
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.