Уникнення анемічної моделі домену - реальний приклад


76

Я намагаюся зрозуміти моделі анемічних доменів і чому вони нібито є анти-зразком.

Ось приклад із реального світу.

У мене є клас Employee, який має безліч властивостей - ім'я, стать, ім'я користувача тощо

public class Employee
{
    public string Name { get; set; }
    public string Gender { get; set; }
    public string Username { get; set; }
    // Etc.. mostly getters and setters
}

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

public class EmployeeLeadRotationService : IEmployeeLeadRotationService
{
     private IEmployeeRepository _employeeRepository;
     // ...plus lots of other injected repositories and services

     public void SelectEmployee(ILead lead)
     {
         // Etc. lots of complex logic
     }
}

Тоді на зворотній стороні форми запиту нашого веб-сайту ми маємо такий код:

public void SubmitForm()
{
    var lead = CreateLeadFromFormInput();

    var selectedEmployee = Kernel.Get<IEmployeeLeadRotationService>()
                                 .SelectEmployee(lead);

    Response.Write(employee.Name + " will handle your enquiry. Thanks.");
}

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

Але для мене незрозуміло, куди повинна йти логіка в службі обертання свинців. Чи це повинно йти в лідерах? Це повинно йти у працівника?

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


Отже, як виглядає ILeadвигляд, якщо в нього не очевидно помістити .SelectEfficiee ()?
Саймон Бучан

Ну, головним у цій справі є веб-запит, тому він матиме властивість Коментарі тощо. Але ми також маємо телефонні запити, програми, ціни тощо, які дещо відрізняються. Інтерфейс ILead мав би такі властивості, як LocationOfLead, TimeOfLead тощо
cbp

1
Тому я думаю, що очевидніше поставити .SelectEfficiee () на провід, ніж працівника, але це не стосується інших проблем: введення залежних сховищ; відсутність SoC; загальна складність наявності всього цього коду SelectEfficiee у класі Lead (насправді нам потрібен буде клас LeadBase, щоб ми не використовували код повторно у всіх класах, що успадковують), коли багато часу (наприклад, під час звітування) ми працюємо насправді не турбує те, як було обрано працівника.
cbp

Може представляти певний інтерес: medium.com/@wrong.about/…
Вадим Самохін

Відповіді:


55

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

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

Прикладом моделі анемічного домену у вашому випадку може бути зовнішній метод

updateHours(Employee emp) // updates the working hours for the employee

який бере об’єкт Employee та оновлює час роботи за тиждень, переконуючись, що прапорці піднімаються, якщо години перевищують певний ліміт. Проблема в тому, що якщо у вас є лише об’єкти Employee, ви не знаєте, як змінити їх робочий час у межах правильних обмежень. У цьому випадку способом боротьби з ним буде переміщення методу updateHours у клас Employee. У цьому полягає суть анти-моделі Anemic Domain Model.


Але що, якщо працівник є постійним об’єктом для бази даних. Чому я повинен вкладати туди метод? Те саме питання справедливе для DTO, де ви не вставляєте методи всередину. Куди б тоді ви поклали метод updateHours?
Паскаль

updateHoursналежить до класу Employee. Ви повинні передати йому будь-які дані, необхідні для оновлення годин, наприклад, завдання, яке було виконано. Об'єкти співавторів теж прекрасні, але бажано відсутні послуги.
MauganRa

30

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

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


5
точно. Це реальний зовнішній модуль (LeadQueueManager або інший) з великою кількістю внутрішньої логіки - це абсолютно не анемічна модель домену. Що знає працівник про планування черги дзвінків? Нічого;)
TomTom

14

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

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

Просто перейменування "RotationService" в щось на зразок "Organization.UserSupportDepartment" робить це очевидним.


0

Якщо ваша модель домену містить лише ролі та речі, а не дії як поведінку, то вона анемічна. Однак я говорю про поведінку стосовно моделі, а не об'єкта . Про різницю між ними я говорю в іншій відповіді ... https://stackoverflow.com/a/31780937/116442

З вашого запитання ви порушуєте мої перші два правила моделювання аналізу домену: -

  1. Поведінка, змодельована як (записана) Діяльність лежить в основі моделі домену. Додайте їх спочатку.
  2. Моделюйте доменні дії як класи, а не методи.

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

EnquiryHandlerModel

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