DDD - правило, що суб'єкти не можуть отримувати прямий доступ до сховищ


185

У Domain Driven Design, здається , є багато з угоди , що Сутності не повинен доступ Сховища безпосередньо.

Це прийшло з книги Еріка Еванса, керованого доменом дизайну , чи це прийшло з іншого місця?

Де є якісь хороші пояснення міркувань, що стоять за ним?

редагувати: Для уточнення: я не говорю про класичну практику OO відділення доступу до даних на окремий рівень від бізнес-логіки - я кажу про специфічну домовленість, згідно з якою в DDD суб'єкти господарювання не повинні спілкуватися з даними рівень доступу взагалі (тобто вони не повинні містити посилання на об'єкти репозиторію)

оновлення: Я дав нагороду БакчеСР, тому що його відповідь здавалася найближчою, але я все ще досить темний з цього приводу. Якщо його такий важливий принцип, десь повинні бути якісь хороші статті про нього в Інтернеті?

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


Погляньте на моє запитання stackoverflow.com/q/8269784/235715 , це показує ситуацію, коли складно вловлювати логіку, без того, щоб Entity мав доступ до сховища. Хоча я думаю, що суб'єкти не повинні мати доступ до сховищ, і є рішення моєї ситуації, коли код можна переписати без посилання на сховища, але наразі я не можу придумати жодного.
Олексій Бурцев

Не знаю, звідки воно взялося. Мої думки: Я думаю, що це непорозуміння походить від людей, як вони не розуміють, що таке DDD. Цей підхід призначений не для впровадження програмного забезпечення, а для його розробки (домен .. дизайн). Ще в ті часи у нас були архітектори та реалізатори, але зараз є лише розробники програмного забезпечення. DDD призначений для архітекторів. А коли архітектор розробляє програмне забезпечення, йому потрібен певний інструмент або зразок для представлення пам'яті або бази даних для розробників, які реалізують підготовлений дизайн. Але сам дизайн (з точки зору бізнесу) не має або не потребує сховища.
Берхалак

Відповіді:


47

Тут трохи плутанини. Репозиторії мають доступ до сукупних коренів. Сукупні корені - це сутності. Причиною цього є відокремлення турбот та хороший розшарування. Це не має сенсу для малих проектів, але якщо ви є великою командою, ви хочете сказати: "Ви отримуєте доступ до продукту через сховище продуктів. Продукт - це сукупний корінь для колекції об'єктів, включаючи об'єкт ProductCatalog. Якщо ви хочете оновити ProductCatalog, ви повинні пройти через ProductRepository. "

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

Але зачекайте! Репозиторій також відноситься до шару стійкості, як у шаблоні репозиторію. У кращому світі сховище Еріка Еванса та шаблон сховища мають окремі імена, оскільки вони, як правило, перекриваються досить небагато. Для отримання шаблону репозиторію вам потрібно контрастувати з іншими способами доступу до даних за допомогою службової шини або системи моделі подій. Зазвичай, коли ви досягнете цього рівня, визначення сховища Еріка Еванса йде в інший бік, і ви починаєте говорити про обмежений контекст. Кожен обмежений контекст, по суті, є власною програмою. Можливо, у вас є складна система затвердження для внесення речей до каталогу товарів. У вашому оригінальному дизайні виріб було центральним, але в цьому обмеженому контексті - каталог товарів. Ви все ще можете отримати інформацію про продукт та оновити продукт через службову шину,

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

Відповідь на коментар 1 : Правильний, хороший запитання. Тому не вся перевірка відбувається в доменному шарі. У Sharp є атрибут "DomainSignature", який робить те, що ви хочете. Це усвідомлює наполегливість, але, будучи атрибутом, зберігає рівень домену чистим. Це гарантує відсутність у вас дубліката суті, у вашому прикладі одного і того ж імені.

Але поговоримо про більш складні правила перевірки. Скажімо, ви Amazon.com. Ви коли-небудь замовляли щось із простроченою кредитною карткою? У мене є, де я не оновив карту і щось купив. Він приймає наказ, і інтерфейс повідомляє мені, що все персико. Приблизно через 15 хвилин мені надійде електронний лист із повідомленням, що є проблема з замовленням, моя кредитна картка недійсна. Тут відбувається те, що в ідеалі є деяка перевірка регулярних виразів у доменному шарі. Це правильний номер кредитної картки? Якщо так, зберігайте замовлення. Однак є додаткове підтвердження на рівні завдань додатків, де запитується зовнішня послуга, щоб перевірити, чи можна здійснити оплату на кредитній картці. Якщо ні, насправді нічого не доставляйте, призупиніть замовлення та чекайте клієнта.

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


15
Дякую. Але я повинен прагнути ввести якомога більше бізнес-логіки в організації (і пов'язані з ними фабрики та технічні характеристики тощо), правда? Але якщо жодному з них заборонено отримувати дані через сховища, то як я повинен писати будь-яку (досить складну) ділову логіку? Наприклад: Користувачеві кімнати чату заборонено змінювати своє ім’я на ім’я, яким вже користувався хтось інший. Я хотів би, щоб це правило було вбудовано об'єктом ChatUser, але це не дуже просто зробити, якщо ви не можете потрапити в сховище звідти. То що мені робити?
codeulike

Моя відповідь була більшою, ніж дозволить поле коментарів, дивіться редагування.
кертоз

6
Ваше підприємство має знати, як захистити себе від шкоди. Це включає переконання, що він не може перейти в недійсний стан. Те, що ви описуєте з користувачем кімнати чату, - це бізнес-логіка, яка існує ДОПОМОГО до тієї логіки, якою суб'єкт господарювання повинен залишатися дійсним. Бізнес-логіка, як те, що ваше бажання насправді належить до послуги Chatroom, а не суб'єкта ChatUser.
Алек

9
Дякую Алеку. Це чіткий спосіб висловити це. Але мені здається, що золотое правило Еванса, зосереджене на домені, "вся бізнес-логіка повинна йти в доменному шарі" суперечить правилу "суб'єкти не мають доступу до сховищ". Я можу з цим жити, якщо розумію, чому це так, але в Інтернеті не можу знайти жодного хорошого пояснення того, чому суб’єкти не повинні отримувати доступ до сховищ. Еванс, схоже, це прямо не згадує. Звідки воно взялося? Якщо ви зможете опублікувати відповідь, що вказує на хорошу літературу, ви, можливо, зможете придбати собі бант на 50 пунктів:)
codeulike

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

35

Спочатку я був переконаний дозволити деяким моїм особам отримати доступ до сховищ (тобто ледачого завантаження без ORM). Пізніше я прийшов до висновку, що не повинен і що я можу знайти альтернативні способи:

  1. Ми повинні знати свої наміри у запиті та те, що ми хочемо від домену, тому ми можемо робити виклики у сховища перед побудовою або викликом поведінки агрегату. Це також допомагає уникнути проблеми непослідовного стану пам'яті та необхідності ледачого завантаження (див. Цю статтю ). Запах полягає в тому, що ви вже не можете створити екземпляр пам’яті своєї організації, не переживаючи про доступ до даних.
  2. CQS (розділ запитів команд) може допомогти зменшити потребу в бажанні викликати сховище для речей в наших об'єктах.
  3. Ми можемо використовувати специфікацію, щоб інкапсулювати та передавати потреби логіки домену та передавати її в сховище (сервіс може упорядкувати ці речі для нас). Специфікація може походити від суб'єкта, який відповідає за підтримку інваріанта. Репозиторій буде інтерпретувати частини специфікації у власну реалізацію запиту та застосовувати правила зі специфікації щодо результатів запитів. Це спрямовано на збереження логіки домену в доменному шарі. Він також краще служить всюдисущої мови та спілкування. Уявіть, що висловлюєте "прострочену специфікацію замовлення" проти того, щоб сказати "замовлення фільтру від tbl_order, у якому розміщений_ат менше 30 хвилин до системного оновлення" (див. Цю відповідь ).
  4. Це ускладнює міркування щодо поведінки суб'єктів, оскільки порушується Принцип єдиної відповідальності. Якщо вам потрібно вирішити питання зберігання / збереження, ви знаєте, куди піти, а куди не їхати.
  5. Це дозволяє уникнути небезпеки надання суб’єкту двостороннього доступу до глобального стану (через сховища та сервіси домену). Ви також не хочете порушувати межу транзакцій.

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

Як правило, ми повинні намагатися уникати використання сховищ (12) всередині агрегатів, якщо це можливо.

Вернон, Вон (2013-02-06). Реалізація дизайну, керованого доменом (Kindle Location 6089). Пірсон освіта. Kindle видання.

А в главі 10 про агрегати в розділі "Модельна навігація" він говорить (одразу після того, як він рекомендує використовувати глобальні унікальні ідентифікатори для посилання на інші сукупні корені):

Посилання за ідентичністю не повністю перешкоджає навігації по моделі. Деякі використовуватимуть сховище (12) всередині агрегату для пошуку. Ця методика називається Disconnected Domain Model, і це фактично форма ледачого завантаження. Однак існує інший рекомендований підхід: Використовуйте сховище або службу доменів (7) для пошуку залежних об'єктів до виклику поведінки сукупності. Служба клієнтських додатків може контролювати це, після чого відправляти в Агрегат:

Він продовжує показувати приклад цього в коді:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

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

Тоді я мав певну дискусію з завжди милозвучним Марко Піветта @ Окраміус, який показав мені трохи коду щодо виведення специфікації з домену та використання цього:

1) Це не рекомендується:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) У доменній службі це добре:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}

1
Питання: Нас завжди навчають не створювати об’єкт у недійсному чи непослідовному стані. Коли ви завантажуєте користувачів із сховища, а потім дзвоните, getFriends()перш ніж робити щось інше, воно буде порожнім або ледачим завантаженим. Якщо порожній, то цей об’єкт лежить і знаходиться в недійсному стані. Будь-які думки з цього приводу?
Джимбо

Репозиторій викликає Домен для створення нового екземпляра. Ви не отримуєте примірника користувача, не пройшовши Домен. Проблема, на яку ця відповідь вирішує, - зворотна. Там, де Домен посилається на сховище, цього слід уникати.
програмамер

28

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

АЛЕ давайте обговоримо. Я думаю, що дуже обгрунтована думка: чому суб'єкт господарювання повинен знати про те, як зберігати іншу сутність? Важливим для DDD є те, що кожна організація несе відповідальність за управління власною «сферою знань» і не повинна нічого знати про те, як читати чи писати інші сутності. Впевнені, що ви, ймовірно, можете просто додати інтерфейс сховища до сутності A для читання об'єктів B. Але ризик полягає в тому, що ви розкриєте знання про те, як зберегти B. Чи буде сутність A також робити перевірку на B, перш ніж зберігати B в db?

Як ви бачите, сутність A може більше втягнутися в життєвий цикл сутності B, що може додати більшої складності моделі.

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

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


1
Гарна думка. Напевно, модель доменної школи, мабуть, матиме суб'єкт В відповідальний за перевірку себе, перш ніж вона дозволить зберегтись, я думаю. Ви впевнені, що Еванс згадує суб'єкти, які не використовують сховища? Я перебуваю на півдорозі книги, і вона ще не згадувала її ...
codeulike

Ну я читав книгу кілька років тому (ну 3 ...), і пам’ять мене не вдається. Я не можу згадати, чи точно він це висловлював, АЛЕ я вважаю, що він це проілюстрував на прикладах. Ви також можете знайти тлумачення прикладу Вантажу (з його книги) у спільноті на сайті dddsamplenet.codeplex.com . Завантажте проект коду (подивіться проект Vanilla - його приклад з книги). Ви побачите, що сховища використовуються лише на рівні програми для доступу до об'єктів домену.
Магнус Баккей

1
Завантаживши приклад DDD SmartCA з книги p2p.wrox.com/…, ви побачите інший підхід (хоча це клієнт Windows RIA), де сховища використовуються в сервісах (тут нічого дивного), але сервіси використовують усередині entites. Це те, що я б не робив, Але я хлопець із веб-додатків. Враховуючи сценарій для програми SmartCA, де ви повинні працювати в режимі офлайн, можливо, дизайн DDD буде виглядати інакше.
Магнус Баккей

Приклад SmartCA звучить цікаво, в якій главі він знаходиться? (завантаження коду влаштовано за главою)
codeulike

1
@codeulike Я зараз розробляю та впроваджую рамки з використанням концепцій ddd. Іноді для перевірки потрібно отримати доступ до бази даних і запитувати її (наприклад: запит на перевірку унікального індексу кількох стовпців). Зважаючи на це і на те, що запити повинні бути записані в шарі сховища. Виходить, що доменним особам потрібно мати посилання на їх інтерфейси сховища в шарі моделі домену, щоб повністю розмістити валідацію в шарі моделі домену. Тож нарешті нормально, щоб доменні особи мали доступ до сховищ?
Карамафрооз

13

Навіщо розділяти доступ до даних?

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

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

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

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

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

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

Цитування:

"Єдина модель зменшує шанси на помилку, оскільки конструкція зараз є прямим зростанням ретельно розглянутої моделі. Дизайн і навіть сам код мають комунікативність моделі".

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

Якщо необхідність доступу до бази даних полягає в таких речах, як перевірка унікальності, погляньте на:

Уді Дахан: найбільші помилки, які роблять команди при застосуванні DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

у розділі "Усі правила не створюються рівними"

і

Використання моделі доменної моделі

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

у розділі "Сценарії невикористання доменної моделі", який стосується тієї самої теми.

Як розділити доступ до даних

Завантаження даних через інтерфейс

"Рівень доступу до даних" був абстрагований через інтерфейс, до якого ви телефонуєте, щоб отримати необхідні дані:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

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

Мінуси: викликовий код повинен припускати, що було завантажено, а що ні.

Скажіть, GetOrderLines повертає об’єкти OrderLine з нульовим властивістю ProductInfo з міркувань продуктивності. Розробник повинен мати глибокі знання про код за інтерфейсом.

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

Тепер відокремлення проблем повинно дозволяти розробнику зосередитися на одному аспекті коду одночасно, наскільки це можливо. Техніка інтерфейсу видаляє, ЯК завантажуються ці дані, але не ЯК МОЖУТЬ завантажуються дані, КОЛИ вони завантажуються, І ДЕ завантажуються.

Висновок: досить низька розділеність!

Ледаче завантаження

Дані завантажуються на вимогу. Виклики для завантаження даних приховані в самому графіку об'єкта, де доступ до властивості може призвести до виконання запиту sql перед поверненням результату.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Плюси: "КОЛИ, де і як" доступ до даних прихований від розробника, орієнтуючись на логіку домену. У сукупності немає коду, який би займався завантаженням даних. Обсяг завантажених даних може бути точним, необхідним кодом.

Мінуси: Коли ви стикаєтеся з проблемою продуктивності, важко це виправити, коли у вас є загальне рішення "один розмір, який відповідає всім". Ледаче завантаження може призвести до погіршення загальної продуктивності, а впровадження ледачого завантаження може бути складним.

Рольовий інтерфейс / швидке отримання

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

Стратегія отримання може виглядати приблизно так:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);
    
        return order;
    }

}
   

Тоді ваш агрегат може виглядати так:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy використовується для створення агрегату, а потім агрегат виконує свою роботу.

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

Мінуси: Розробник все ще повинен коригувати / переглянути стратегію отримання після зміни доменного коду.

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


Дякую, я перевірю посилання. Але у своїй відповіді ви плутаєте "розділення проблем" з "взагалі немає доступу до нього"? Звичайно, більшість людей погодиться з тим, що стійкий шар слід зберігати окремо від шару, на якому знаходяться Суб'єкти. Але це інакше, як сказати: "Суб'єкти не повинні мати можливість бачити стійкий шар навіть через дуже загальну реалізацію-агностик інтерфейс '.
codeulike

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

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

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


12

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

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

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

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

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

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

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

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

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

SetStatus()тепер є проста залежність від реєстратора, який, очевидно, буде виконувати частину роботи .

Для останнього, щоб полегшити роботу з кодом клієнта, ми можемо замість цього увійти через IInvoiceService. Зрештою, реєстрація рахунків-фактур виглядає досить суттєвою для рахунку-фактури. Такий сингл IInvoiceServiceдопомагає уникнути необхідності у всіляких міні-сервісах для різних операцій. Мінусом є те, що стає незрозумілим, що саме буде робити ця послуга . Це може навіть почати виглядати як подвійне відправлення, в той час як більша частина справді все ще робиться SetStatus()сама по собі.

Ми все ще можемо назвати параметр «реєстратор», сподіваючись розкрити наш намір. Здається, трохи слабко.

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

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

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

Насправді це дає альтернативу постійно клопітким лінивим навантаженням .

Оновлення: Текст нижче я залишив для історичних цілей, але пропоную відмовитися від ледачих навантажень на 100%.

Для справжніх, на основі властивостей ледачих навантажень, я дійсно в даний час використовують ін'єкції конструктора, але в інерційності-неосвіченим чином.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

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

З іншого боку, код, який створює фактично новий, Invoice буде просто передавати функцію, яка повертає порожній список:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(Звичай ILazy<out T>може позбавити нас від негарної ролі IEnumerable, але це ускладнить дискусію.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

Буду радий почути вашу думку, вподобання та вдосконалення!


3

Як мені здається, це є загальноприйнятою практикою, пов’язаною з ООД, а не специфічною для DDD.

Я можу придумати такі причини:

  • Розділення проблем (Суб'єкти повинні бути відокремлені від способу їх існування. Оскільки може існувати декілька стратегій, в яких одна і та ж організація буде зберігатися залежно від сценарію використання)
  • За логікою, об'єкти можна побачити на рівні нижче рівня, на якому працюють сховища. Компоненти нижчого рівня не повинні мати знання про компоненти вищого рівня. Тому записи не повинні мати знань про сховищах.

2

просто Вернон Вон дає рішення:

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


Але не від Сутності.
ssmith

Від джерела IDDD Вернона Вона: Календар загального класу розширює EventSourcedRootEntity {... громадський календарПрограма вступуКалендаріВ'їзд (CalendarIdentityService aCalendarIdentityService,
Теймураз

перевірити його статтю @Teimuraz
Аліреза Рахмані Халілі

1

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

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

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

В даний час я використовую 3 шари (GUI, Logic, Access Access), як це робить багато розробників, оскільки це хороша техніка.

Відокремлення даних у Repositoryшар (він же Data Accessшар) може сприйматися як хороша техніка програмування, а не просто правило, яке слід дотримуватися.

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

Цитата: «Іліада» не була повністю винайдена Гомером, Карміна Бурана не була повністю винайдена Карлом Орффом, і в обох випадках людина, яка поклала на роботу інших людей, отримала заслугу ;-)


1
Дякую, але я не запитую про відділення доступу до даних від логіки бізнесу - це дуже зрозуміла річ, щодо якої існує дуже широка згода. Я запитую про те, чому в архітектурах DDD, таких як S # arp, Субстанції не можуть навіть «спілкуватися» з рівнем доступу до даних. Це цікаве домовленість, про яке я не зміг знайти багато обговорень.
codeulike

0

Це прийшло з книги Еріка Еванса, керованого доменом дизайну, чи це прийшло з іншого місця?

Це старі речі. Книга Еріка просто зробила це ще більше.

Де є якісь хороші пояснення міркувань, що стоять за ним?

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

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

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

редагувати: Для уточнення: я не говорю про класичну практику OO відділення доступу до даних на окремий рівень від бізнес-логіки - я кажу про специфічну домовленість, згідно з якою в DDD суб'єкти господарювання не повинні спілкуватися з даними рівень доступу взагалі (тобто вони не повинні містити посилання на об'єкти репозиторію)

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


Правильно. Отже, яким чином цілком наполегливий ігноруючий суб'єкт господарювання реалізує Business Logic, якщо їй навіть не дозволяється спілкуватися зі стійким шаром? Що він робить, коли йому потрібно переглянути значення у довільних інших сутностях?
codeulike

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

0

Напрошуючи Кароліну Ліліенталь, "Шаблони повинні запобігати циклам" https://www.youtube.com/watch?v=eJjadzMRQAk , де вона посилається на циклічні залежності між класами. У випадку сховищ всередині агрегатів існує спокуса створити циклічні залежності з зручності навігації по об'єктах як єдиної причини. Шаблон, згаданий вище програхамером, який був рекомендований Верноном Вон, де інші агрегати посилаються на ідентифікатори замість кореневих екземплярів, (чи існує назва цього шаблону?), Пропонується альтернатива, яка може вказувати на інші рішення.

Приклад циклічної залежності між класами (сповідь):

(Час0): Два класи, Зразок та Добре, відносяться один до одного (циклічна залежність). Добре стосується зразка, а Sample повертається до свердловини поза зручністю (іноді циклічні зразки, іноді циклічні всі лунки в тарілці). Я не міг уявити собі випадків, коли Зразок не посилався б на колодязь, де він розміщений.

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

Мені також допомогла згадана вище стаття про негативні аспекти ледачого навантаження.


-1

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


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