Моделі багатого домену - як, зокрема, підходить поведінка?


84

У дискусіях про доменні моделі Річ проти Анемій Інтернет є сповненим філософських порад, але коротким на авторитетні приклади. Мета цього питання - знайти остаточні вказівки та конкретні приклади правильних моделей, керованих доменом. (Ідеально в C #.)

На прикладі реального світу ця реалізація DDD здається помилковою:

Наведені нижче моделі доменів WorkItem - це не що інше, як пакети властивостей, використовувані Entity Framework для першої бази даних з кодом. За Фаулером, це анемічно .

Шар WorkItemService, мабуть, є поширеним неправильним сприйняттям Доменних служб; він містить всю логіку поведінки / бізнесу для WorkItem. За Ємельяновим та іншими, це процесуально . (стор. 6)

Тож якщо нижче неправильне, як я можу зробити це правильно?
Поведінка, тобто AddStatusUpdate або Checkout , повинна належати правильному класу WorkItem?
Які залежності повинна мати модель WorkItem?

введіть тут опис зображення

public class WorkItemService : IWorkItemService {
    private IUnitOfWorkFactory _unitOfWorkFactory;

    //using Unity for dependency injection
    public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void AddStatusUpdate(int workItemId, int statusId) {

        using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
            var workItemRepo = unitOfWork.WorkItemRepository;
            var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;

            var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
            if (workItem == null)
                throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(Цей приклад був спрощений, щоб він був більш читабельним. Код, безумовно, все ще незграбний, оскільки це заплутана спроба, але поведінка домену була: оновити статус, додавши новий статус в історію архіву. Зрештою я згоден з іншими відповідями, це міг би просто опрацювати CRUD.)

Оновлення

@AlexeyZimarev дав найкращу відповідь, ідеальне відео на цю тему в C # від Jimmy Bogard, але це, очевидно, було перенесено в коментар нижче, оскільки він не дав достатньо інформації за посиланням. У мене є приблизний проект моїх записок, узагальнюючи відео у своїй відповіді нижче. Будь ласка, коментуйте відповідь з будь-якими виправленнями. Відео триває годину, але його дуже варто переглянути.

Оновлення - через 2 роки

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

введіть тут опис зображення

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


6
Усі філософські теорії падають прямо на землю, коли ви їх розповідаєте "I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll". "Entities" в жаргоні Entity Framework - це не те саме, що "Entities", як у "Domain Domain"
Federico Berasategui

Я все в порядку з копіюванням моїх об'єктів домену в DTO, використовуючи автоматизований інструмент типу Automapper, якщо це потрібно. Я просто не впевнений, як це має виглядати наприкінці дня.
RJB

16
Я рекомендую вам переглянути сесію NDC 2012 року Jimmy Bogard "Створення злих моделей доменів" на Vimeo . Він пояснює, яким повинен бути багатий домен, і як реалізувати їх у реальному житті, маючи поведінку у ваших організаціях. Приклади дуже практичні, і всі вони є на C #.
Олексій Зімарев

Дякую, я перебуваю на півдорозі відео, і це ідеально до цих пір. Я знав, що якщо це неправильно, десь там повинна бути «правильна» відповідь ....
RJB

2
Я вимагаю також любові до Яви: /
uylmz

Відповіді:


59

Найбільш корисну відповідь дав Олексій Зімарьов і отримав щонайменше 7 відгуків, перш ніж модератор перемістив його в коментар під моїм початковим запитанням ....

Його відповідь:

Я рекомендую вам переглянути сесію NDC 2012 року Jimmy Bogard "Створення злих моделей доменів" на Vimeo. Він пояснює, яким повинен бути багатий домен, і як реалізувати їх у реальному житті, маючи поведінку у ваших сутностях. Приклади дуже практичні, і все це на C #.

http://vimeo.com/43598193

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

  • "Для більшості програм ... ми не знаємо, що вони будуть складними, коли ми починаємо. Вони просто стають таким".
    • Складність зростає природним чином із додаванням коду та вимог. Програми можуть запускатися дуже просто, як CRUD, але поведінка / правила можуть бути виправлені.
    • "Приємно, що ми не повинні починати складні. Ми можемо почати з анемічної доменної моделі. Це лише сумки власності, і лише за допомогою стандартних методів рефакторингу ми можемо рухатися до справжньої доменної моделі".
  • Моделі доменів = бізнес-об’єкти. Поведінка домену = бізнес-правила.
  • Поведінка часто приховується в додатку - це може бути у PageLoad, Button1_Click або часто в допоміжних класах, таких як "FooManager" або "FooService".
  • Правила бізнесу, які є окремими від об'єктів домену, "вимагають від нас пам'ятати" ці правила.
    • У моєму особистому прикладі вище, одне правило бізнесу - WorkItem.StatusHistory.Add (). Ми не просто змінюємо статус, ми архівуємо його для аудиту.
  • Поведінка домену "усуває помилки в додатку набагато простіше, ніж просто написати купу тестів". Тести вимагають, щоб ви знали, щоб написати ці тести. Поведінка домену пропонує вам правильні шляхи тестування .
  • Доменні служби - це "допоміжні класи для координації діяльності між різними об'єктами доменної моделі".
    • Доменні послуги! = Поведінка домену. Суб'єкти мають поведінку, доменні служби є лише посередниками між суб'єктами.
  • Об'єкти домену не повинні мати необхідну інфраструктуру (тобто IOfferCalculatorService). Служба інфраструктури повинна бути передана до доменної моделі, яка її використовує.
  • Моделі доменів повинні запропонувати вам сказати, що вони вміють робити, і вони повинні вміти робити лише те.
  • Властивості доменних моделей повинні охоронятись приватними програмами, щоб лише модель могла встановлювати свої властивості за допомогою власних поведінок . Інакше це "розбещеність".
  • Об'єкти анемічної доменної моделі, які є лише пакетами властивостей для ORM, - це лише "тонка шпон - сильно набрана версія над базою даних".
    • "Як би просто не було вставити рядок бази даних в об'єкт, це те, що ми маємо."
    • "Найбільш стійкі об'єктні моделі - це саме це. Те, що відрізняє анемічну модель домену від програми, яка насправді не має поведінки, полягає в тому, якщо об’єкт має бізнес-правила, але ці правила не знайдені в доменній моделі. '
  • "Для багатьох програм немає реальної потреби в побудові будь-якого реального логічного рівня бізнес-додатків. Це просто щось, що може поспілкуватися з базою даних і, можливо, якийсь простий спосіб представити дані, які є там".
    • Інакше кажучи, якщо все, що ви робите, це CRUD без спеціальних бізнес-об'єктів чи правил поведінки, вам не потрібен DDD.

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


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

6

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

Наприклад, якщо існувала вимога, що певний робочий елемент може мати лише конкретні статуси, або він може мати лише N статусів, то це доменна логіка і повинен бути частиною WorkItemабо StatusHistoryметоду, або як його.

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

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

Нарешті, я можу зазначити ще одне, що стосується ОТ, що справжню модель домену з реальним дизайном OOP було б дуже важко зберегти за допомогою Entity Framework. Хоча ORM розроблялися з відображенням істинної структури OOP на реляційну, все ще існує багато проблем, і реляційна модель часто просочується в модель OOP. Навіть із nHibernate, який я вважаю набагато потужнішим, ніж EF, це може бути проблемою.


Хороші бали. Де тоді належав би метод AddStatusUpdate у Дані чи іншому проекті інфраструктури? Що є прикладом будь-якої поведінки, яка теоретично може належати WorkItem? Будемо дуже вдячні за будь-який псуедо-код або макет. Мій приклад був насправді спрощений, щоб він був більш читабельним. Є й інші об'єкти, наприклад, AddStatusUpdate має деяку додаткову поведінку - він фактично приймає назву категорії статусу, а якщо цієї категорії не існує, категорія створюється.
RJB

@RJB Як я вже сказав, AddStatusUpdate - це код, який використовує домен. Отже, або якась веб-служба або додаток, який використовує класи домену. І як я вже говорив, ви не можете очікувати будь-якого макету або псевдокоду, тому що вам потрібно буде зробити цілий проект досить великої складності, щоб показати реальну перевагу доменної моделі OOP.
Ейфорія

5

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

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

Одним із прикладів цього може бути веб-сервіс SOAP на базі .NET, який спілкується з клієнтською програмою Silverlight, яка має DLL, що містить прості типи даних. Цей проект доменної сутності може бути вбудований у .NET-збірку або збірку Silverlight, де зацікавлені компоненти Silverlight, у яких є ця DLL, не будуть піддаватися поведінці об'єктів, яка може залежати від компонентів, доступних лише для сервісу.

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


1
1) Як можна відокремити бізнес-логіку від доменної моделі? Це сфера, в якій живе ця бізнес-логіка; суб'єкти в цьому домені виконують поведінку, пов'язану з цією логікою бізнесу. У реальному світі немає жодних служб, а також вони не існують в головах експертів з домену. 2) Будь-який компонент, який бажає інтегруватися з вами, повинен створити свою власну модель домену, оскільки її потреби будуть відрізнятися, і він матиме інший погляд на вашу модель домену. Це давній фальшивість, що ви можете створити одну модель домену, якою можна ділитися навколо.
Стефан Білліет

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

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

@StefanBilliet У ідеальному світі я згоден з вами, коли бізнес-експерт має час сісти з розробниками. Реальність індустрії програмного забезпечення полягає в тому, що бізнес-експерт не має часу або інтересу брати участь на цьому рівні або ще гірше, але, як очікується, розробники просто розберуться з цим лише невиразними вказівками.
maple_shaft

Щоправда, але це не привід сприймати цю реальність. Продовжувати таку гонитву - це витрачати час (а можливо і репутацію) розробників та гроші замовника. Описаний мною процес - це відносини, які потрібно будувати з часом; це вимагає великих зусиль, але це дає набагато кращі результати. Існує причина, що "всюдисуща мова" часто вважається найважливішим аспектом DDD.
Стефан Більяет

5

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

Інкапсулюйте "зміну статусу робочих предметів" WorkItemкласу так:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

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

Ми змінюємо це на щось подібне:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

Реалізація різко змінилася, але абонент ChangeStatusметоду не знає про основні деталі реалізації та не має підстав змінювати себе.

Це приклад багатючої моделі домену IMHO.

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