Де нам слід поставити валідацію доменної моделі


38

Я все ще шукаю найкращу практику для перевірки доменної моделі. Це добре, щоб включити перевірку в конструктор доменної моделі? приклад перевірки моделі мого домену наступним чином:

public class Order
 {
    private readonly List<OrderLine> _lineItems;

    public virtual Customer Customer { get; private set; }
    public virtual DateTime OrderDate { get; private set; }
    public virtual decimal OrderTotal { get; private set; }

    public Order (Customer customer)
    {
        if (customer == null)
            throw new  ArgumentException("Customer name must be defined");

        Customer = customer;
        OrderDate = DateTime.Now;
        _lineItems = new List<LineItem>();
    }

    public void AddOderLine //....
    public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}


public class OrderLine
{
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal UnitPrice { get; set; }

    public OrderLine(Order order, int quantity, Product product)
    {
        if (order == null)
            throw new  ArgumentException("Order name must be defined");
        if (quantity <= 0)
            throw new  ArgumentException("Quantity must be greater than zero");
        if (product == null)
            throw new  ArgumentException("Product name must be defined");

        Order = order;
        Quantity = quantity;
        Product = product;
    }
}

Дякую за всі ваші пропозиції.

Відповіді:


47

Є цікава стаття Мартіна Фаулера на цю тему, яка висвітлює аспект, який більшість людей (включаючи мене) схильний не помічати:

Але одне, на що я думаю, що постійно обтяжує людей, це те, коли вони думають, що дійсність об'єкта є незалежним від контексту, наприклад методом isValid.

Я думаю, що набагато корисніше думати про валідацію як про щось, що пов'язане з контекстом - як правило, дією, яку ви хочете зробити. Чи дійсне це замовлення для заповнення, чи дійсний цей клієнт для реєстрації в готелі. Тому замість таких методів, як isValid, є такі методи, як isValidForCheckIn.

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

Знову із статті:

У програмі About Face Алан Купер висловився за те, що ми не повинні дозволяти нашим уявленням про дійсні стани перешкоджати користувачеві вводити (і зберігати) неповну інформацію. Про це мені нагадали кілька днів тому, коли я читав проект книги, над якою працює Джиммі Нільссон. Він заявив про принцип, що ви завжди зможете зберегти об'єкт, навіть якщо в ньому є помилки. Хоча я не переконаний, що це має бути абсолютним правилом, але я думаю, що люди, як правило, заважають економити більше, ніж потрібно. Обдумування контексту для перевірки може допомогти запобігти цьому.


Слава богу, хтось сказав це. Форми, які містять 90% даних, але нічого не врятують, є несправедливими до користувачів, які часто складають інші 10%, щоб не втратити дані, тому вся перевірка зроблена - змушує систему втратити 10% був складений. Подібні проблеми можуть траплятися із зворотнього боку - скажімо, імпорт даних. Я виявив, що зазвичай краще спробувати працювати належним чином з недійсними даними, ніж намагатися не допустити цього.
psr

2
@psr Вам навіть потрібна зворотна логіка, якщо ваші дані не зберігаються? Ви можете залишити всі маніпуляції на стороні клієнта, якщо ваші дані не мають значення для вашої бізнес-моделі. Також буде марною витратою ресурсів для надсилання повідомлень назад (клієнт - сервер), якщо дані безглузді. Тож ми повертаємося до ідеї "ніколи не дозволяючи доменним об’єктам вводити недійсний стан!" .
Гео C.

2
Цікаво, чому так багато голосів за таку неоднозначну відповідь. Під час використання DDD іноді існують деякі правила, які просто перевіряють, чи є деякі дані INT або вони знаходяться в діапазоні. Наприклад, коли ви дозволяєте користувачеві програми вибирати деякі обмеження щодо його продуктів (скільки разів хтось може переглядати мій продукт і в який інтервал днів у місяці). Тут обидва обмеження повинні бути int, і одне з них має бути в діапазоні 0-31. Здається, перевірка формату даних, яка в середовищі, що не відповідає DDD, вписується в службу чи контролер. Але в DDD я на боці збереження валідайону в домені (90% від нього).
Гео К.

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

PS Я не змішую авторизацію (дозволено щось робити), автентифікацію (чи повідомлення прийшло з потрібного місця або було надіслано правильним клієнтом, ідентифікувались за допомогою ключа api / токена / імені користувача чи будь-чого іншого) з підтвердженням формату або ділові правила. Коли я кажу 90%, я маю на увазі ті правила бізнесу, які більшість з них також включає перевірку формату. Перевірка формату курсу може бути у верхніх шарах, але більшість з них буде у домені (навіть формат адреси електронної пошти, який буде підтверджений у об’єкті значення EmailAddress).
Гео C.

6

Незважаючи на те, що це питання трохи несвіжий, я хотів би додати щось варте:

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

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

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


1
Багато разів об'єкти здаються важкими для побудови. Наприклад, у цьому випадку ви можете обійти відкритий конструктор, створивши клас Wrapper, який успадковується від тестованого класу та дозволяє створити екземпляр базового об’єкта в недійсному стані. Саме тут використання правильних модифікаторів доступу для класів та конструкторів вступає в гру і може дійсно шкодити тестуванню при неправильному використанні. Додатково уникати «запечатаних» класів та методів, за винятком випадків, коли це доречно, пройде довгий шлях до полегшення тестування коду.
P. Roe

4

Як я впевнений, ви вже знаєте ...

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

Перевірка достовірності даних, переданих як параметри c'tor, безумовно, діє в конструкторі - інакше ви, можливо, дозволяєте побудувати недійсний об'єкт.

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


Крім того, як бічна примітка, коли ви включили її до свого зразка коду ...

Якщо ви не здійснюєте подальшу перевірку (чи іншу функціональність) в AddOrderLine, я, швидше за все, викрию ці List<LineItem>властивість, а не Orderвиступаю як фасад .


Навіщо виставляти контейнер? Що має значення для верхніх шарів, що таке контейнер? Цілком розумно мати AddLineItemметод. Насправді для DDD це є кращим. Якщо List<LineItem>змінити на спеціальний об’єкт колекції, то відкриті властивості та все, що залежало від List<LineItem>властивості, можуть бути змінені, помилки та винятки.
IАнотація

4

Валідацію слід проводити якомога швидше.

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

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

  1. Перевірка властивості перевіряє, чи є значення для цього властивості правильним, наприклад, коли очікується діапазон між 1-10.

  2. Перевірка об'єкта гарантує, що всі властивості об’єкта є дійсними спільно один з одним. наприклад, BeginDate є перед EndDate. Припустимо, ви читаєте значення з сховища даних, і і BeginDate і EndDate ініціалізуються до DateTime.Min за замовчуванням. Встановлюючи BeginDate, немає причин застосовувати правило "повинно бути до кінця", оскільки це не застосовується. Це правило слід перевірити ПІСЛЯ всіх властивостей встановлено. Це можна назвати на сукупному кореневому рівні

  3. Перевірка також повинна бути попередньо оформлена на сукупному (або сукупному кореневому) об'єкті. Об'єкт замовлення може містити дійсні дані, і так це роблять OrderLines. Але тоді ділове правило зазначає, що жодне замовлення не може перевищувати 1000 доларів. Як би ви застосували це правило, якщо в деяких випадках це дозволено. ви не можете просто додати властивість "не підтверджувати суму", оскільки це призведе до зловживань (рано чи пізно, можливо, навіть ви, просто для того, щоб уникнути цього "бридкого запиту").

  4. далі відбувається валідація на презентаційному шарі. Ви дійсно збираєтесь надіслати об’єкт по мережі, знаючи, що він не вдасться? Або ви запасите користувачеві цей бурдон і повідомте його, як тільки він введе недійсне значення. наприклад, у більшості випадків ваше середовище DEV буде повільніше, ніж виробництво. Ви хочете зачекати 30 секунд, перш ніж вас поінформують про "ви забули це поле ПРОТИ під час ще ДРУГОГО тестового запуску", особливо коли є помилка виробництва, яку потрібно виправити, коли ваш бос дихає вам за шию?

  5. Перевірка на рівні постійності повинна бути максимально наближеною до валідації вартості майна. Це допоможе запобігти виняткам з читанням помилок чи «недійсних значень» під час використання картографів будь-якого типу або звичайних зчитувачів старих даних. Використання збережених процедур вирішує цю проблему, але вимагає написати ту саму логіку перевірки AGAIN та виконати її ПРОТИ. І збережені процедури - це домен адміністратора БД, тому не намагайтеся також виконувати свою роботу (або ще гірше, не турбуйте його цим "солодким вибором, за який він не платить".

тож сказати це деякими відомими словами "це залежить", але принаймні зараз ви знаєте, ЧОМУ це залежить.

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

З цієї причини я викидаю лише винятки валідації рівня властивості. На всіх інших рівнях я використовую ValidationResult методом IsValid, щоб зібрати всі "порушені правила" та передати їх користувачеві в одному AggregateException.

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

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

саме тому я також згадав про перевірку презентації, щоб якомога коротше замикати їх.

У випадку, якщо вам цікаво, чому я також маю валідацію на рівні агрегації (або рівень обслуговування, якщо вам подобається), це тому, що у мене немає кришталевої кулі, яка б мені сказала, хто буде використовувати мої послуги в майбутньому. У вас буде достатньо проблем з пошуку власних помилок, щоб не допустити інших помилок ваших :), ввівши недійсні дані, наприклад, ви адмініструєте додаток A, але додаток B подає деякі дані за допомогою вашої служби. Здогадайтесь, кого вони запитують спочатку, коли є помилка? Адміністратор програми B із задоволенням повідомить користувача "немає помилок в моєму кінці, я просто подаю дані".

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