Чи є дійсно агрегати DDD у веб-додатку?


40

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

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

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

Якщо я розуміння агрегати правильно, я зазвичай використовую шаблон сховища повернути OrderAggregate , який буде містити елементи GetAll, GetByID, Deleteі Save. Гаразд, це звучить добре. Але ...

Якщо я закликаю GetAll перерахувати всі мої замовлення, мені здається, що ця модель вимагає повернення всього списку сукупної інформації, повних замовлень, рядків замовлення тощо ... Коли мені потрібна лише невелика підмножина цієї інформації (лише інформація заголовка).

Я щось пропускаю? Або є якийсь рівень оптимізації, який ви б тут використали? Я не можу уявити, щоб хтось виступав за повернення цілих сукупностей інформації, коли вона вам не потрібна.

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

Хтось може мені це уточнити?

Редагувати:

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

Фаулер визначає сховище як сховище даних, яке використовує семантику колекції, і, як правило, зберігається в пам'яті. Це означає створення цілого графіка об'єкта.

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

Більшість людей, схоже, вважають сховища прославленими об’єктами доступу до даних, де ви просто створюєте методи для отримання будь-яких даних, які ви хочете. Це, мабуть, не є наміром, як описано у Шаблонах Фаулера архітектури прикладних програм підприємства.

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

Я думаю, відповідь полягає в тому, що це набагато складніше поняття, ніж я вперше подумав.


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

для вашої ситуації ви можете створити проксі-сервер для сукупного кореневого об'єкта, який вибірково витягує та кешує дані лише тоді, коли це вимагається
Стівен А. Лоу,

Моя пропозиція - реалізувати ледаче навантаження в асоціаціях кореневих агрегатів. Таким чином, ви можете отримати список коренів, не завантажуючи занадто багато об'єктів.
Джуліано

4
майже через 6 років, все ще гарне питання. Прочитавши розділ червоної книги, я б сказав: не робіть своїх агрегатів занадто великими. Заманливо вибрати якусь концепцію вищого рівня зі свого домену та оголосити її коренем до правила Them All, окрім DDD виступає за менші сукупності. І пом'якшує неефективність, як та, яку ви описали вище.
Cpt. Senkfuss

Ваші агрегати повинні бути якомога меншими, одночасно бути природними та ефективними для домену (виклик!). Крім того, для ваших репостів цілком чудово і бажано використовувати досить специфічні методи.
Тимо

Відповіді:


30

Не використовуйте свою модель домену та агрегати для запитів.

Насправді, те, що ви задаєте, є досить поширеним питанням, що було встановлено набір принципів та зразків, щоб уникнути саме цього. Це називається CQRS .


2
@Mystere Man: Ні, це для забезпечення мінімальних необхідних даних. Це одне з великих цілей окремої моделі читання. Це також допомагає вирішити деякі проблеми одночасності. CQRS має ряд переваг при застосуванні до DDD.
квентін-зірин

2
@Mystere: Я дуже здивований, що ви пропустите це, якщо прочитаєте статтю, до якої я посилався. Дивіться розділ під назвою "Запити (звітування)": "Коли програма вимагає отримання даних ... це потрібно зробити в один виклик до рівня Запиту, а взамін він отримає один єдиний DTO, що містить усі необхідні дані ... Причиною цього є те, що дані, як правило, запитуються в багато разів більше, ніж виконується поведінка домену, тому оптимізуючи це, ви підвищите сприйману продуктивність системи ".
квентин-зірин

4
Ось чому ми б не використовували Репозиторій для читання даних у системі CQRS. Ми б написали простий клас для інкапсуляції запиту (використовуючи будь-який технічний варіант, який був зручним чи потрібним; часто тут підходить прямий ADO.Net або Linq2Sql або SubSonic), повертаючи (усі) дані, необхідні для виконання завдання, щоб уникнути перетягування даних через усі звичайні шари сховища DDD. Ми будемо використовувати Репозиторій для отримання Агрегату лише в тому випадку, якщо ми хотіли відправити команду домену.
квентін-зірин

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

2
@qes Дійсно, найкраще рішення - не використовувати DDD для запитів (читати) :) Але ви все одно використовуєте DDD у командній частині, тобто для зберігання чи оновлення даних. Тож у мене є питання до вас, чи завжди ви використовуєте сховища з сутностями, коли вам потрібно оновити дані в БД? Скажімо, вам потрібно змінити лише одне невелике значення у стовпці (якийсь перемикач), чи все-таки завантажуєте цілий Entity у рівень програми, змінюєте одне значення (властивість), а потім зберігаєте цілий Entity назад у БД? Трохи надмірності теж?
Андрій

8

Я боровся і все ще боюся з тим, як найкраще використовувати шаблон сховища в дизайні, керованому доменом. Після першого використання я придумав такі практики:

  1. Репозиторій повинен бути простим; він несе відповідальність лише за зберігання доменних об'єктів та їх вилучення. Вся інша логіка повинна бути в інших об'єктах, таких як фабрики та доменні служби.

  2. Репозиторій веде себе як колекція так, ніби це колекція пам'яті сукупних коренів.

  3. Репозиторій не є загальним DAO, кожен сховище має свій унікальний і вузький інтерфейс. У сховищі часто є конкретні методи пошуку, які дозволяють шукати колекцію з точки зору домену (наприклад: дати мені всі відкриті замовлення для користувача X). Сам сховище можна реалізувати за допомогою загального DAO.

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

  5. Такі варіанти залежать від використовуваних технологій, оскільки вам потрібно знайти спосіб найбільш ефективно висловити свою модель домену за допомогою використовуваних технологій.


Це, безумовно, складна тема точно. Важко перетворити теорію в практику, особливо коли її об'єднання двох чітких і окремих теорій стає єдиною практикою.
Себастьян Паттен

6

Я не думаю, що ваш метод GetOrderHeaders взагалі перемагає призначення сховища.

DDD піклується (серед іншого) про те, щоб ви отримали те, що вам потрібно, через сукупний корінь (наприклад, у вас не було б OrderDetailsRepository), але це не обмежує вас у тому, що ви згадуєте.

Якщо OrderHeader - це концепція домену, то вам слід визначити його таким і мати відповідні методи сховища для їх отримання. Просто переконайтеся, що ви переходите через правильний сукупний корінь.


Можливо, тут я плутаю поняття, але моє розуміння шаблону сховища полягає в тому, щоб відокремити стійкість від домену, використовуючи стандартний інтерфейс для стійкості. Якщо вам доведеться додати спеціальні методи для певної функції, вони, здається, знову з'єднують речі.
Ерік Функенбуш

1
Сталість механізм від'єднується від домену, але не те , що в даний час зберігається. Якщо вам здається, що ви говорите такі речі, як "нам потрібно перерахувати заголовки замовлень тут", то вам потрібно змоделювати OrderHeader у вашому домені та запропонувати спосіб отримати їх із вашого сховища.
Ерік Кінг

Крім того, не зациклюйтесь на "стандартному інтерфейсі для наполегливості". Не існує такого поняття, як загальний шаблон сховища, якого вистачить для всіх можливих додатків. У кожному додатку буде багато методів сховища, що перевищують стандартні "GetById", "Зберегти" тощо. Ці методи є початковою, а не кінцевою.
Ерік Кінг

4

Моє використання DDD не може вважатися "чистим" DDD, але я адаптував наступні реальні стратегії використання DDD проти сховища даних БД.

  • Сукупний корінь має пов'язане сховище
  • Асоційований сховище використовується лише цим агрегатним коренем (він не є загальнодоступним)
  • Репозиторій може містити виклики запитів (наприклад, GetAllActiveOrders, GetOrderItemsForOrder)
  • Служба виставляє публічну підмножину сховища та інші нераціональні операції (наприклад, переказ грошей з одного банківського рахунку на інший, LoadById, пошук / пошук, CreateEntity тощо).
  • Я використовую корінь Root -> Service -> Repository. Служба DDD передбачається використовувати лише для того, що суб'єкт господарювання не може відповісти сам (наприклад, LoadById, TransferMoneyFromAccountToAccount), але в реальному світі я схильний також втримуватися в інших службах, пов'язаних з CRUD (Зберегти, Видалити, Запити), навіть якщо root повинен мати можливість "відповідати / виконувати" ці самі. Зауважте, що немає нічого поганого в наданні суб'єкту доступу до іншої сукупної кореневої служби! Однак пам’ятайте, що ви не включили б у сервіс (GetOrderItemsForOrder), але включили б його у сховище, щоб агрегатний корінь міг ним скористатися. Зауважте, що сервіс не повинен відкривати будь-які відкриті запити, такі як сховище.
  • Я зазвичай визначаю репозиторій абстрактно у доменній моделі (через інтерфейс) і забезпечую окрему конкретну реалізацію. Я повністю визначаю послугу в доменній моделі, яка вводиться в конкретне сховище для її використання.

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

У вашому прикладі я, мабуть, надішлю виклик сховища, який приніс лише заголовки замовлення, якби я хотів завантажити дані в окремий виклик. Зауважте, що маючи "OrderHeader", ми фактично вводимо додаткову концепцію в домен.


3

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

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

Для вашого прикладу, сервісний рівень відкриє таку операцію, як GetOrdersForCustomer, яка повертає лише те, що потрібно для перегляду зведеного списку замовлень (як ви їх називаєте OrderHeaders).

Нарешті, шаблон репозиторію НЕ ОБ'ЄДНА колекція, але також дозволяє декларативні запити. У C # ви можете використовувати LINQ як об'єкт запиту , або більшість інших O / RM також надають специфікацію об'єкта запиту.

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

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

Сподіваюсь, це допомагає з’ясувати речі.


0

Я знаю, що це давнє питання, але, здається, я дійшов до іншої відповіді.

Коли я створюю сховище, він зазвичай переносить деякі кешовані запити.

Фаулер визначає сховище як сховище даних, яке використовує семантику колекції, і, як правило, зберігається в пам'яті. Це означає створення цілого графіка об'єкта.

Зберігайте ці сховища у вашому сервері таран. Вони не просто передають об'єкти в базу даних!

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

На даний момент у вас є два варіанти.

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

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

Насправді те, як ви це реалізуєте, це саме те, і деталізація реалізації. Якщо у мого найбільшого користувача є 10 замовлень, я, мабуть, хочу перейти з варіантом 2. Якщо я кажу 10 000 замовлень, тоді потрібен варіант 1. І в вищезазначених випадках, і в багатьох інших випадках я хочу, щоб сховище приховувало цю детальну інформацію про реалізацію.

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

На мій досвід, агрегати домену пропонують величезні переваги.

  • Вони є величезною частиною повторного використання коду, яка фактично працює.
  • Вони спрощують код, зберігаючи вашу ділову логіку прямо в основному шарі, замість того, щоб прокручувати інфраструктурний рівень, щоб отримати сервер sql для цього.
  • Вони також можуть значно прискорити час відповідей, зменшивши кількість запитів, які потрібно зробити, оскільки ви можете їх легко кешувати.
  • SQL, про який я пишу, часто набагато рентабельніший, оскільки я часто просто прошу про все і обчислюю сторону сервера.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.