Чи все-таки справедливо говорити про анемічну модель в контексті функціонального програмування?


40

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

Я не дуже досвідчений в чистому функціональному програмуванні, але хотів би знати, як DDD вписується в парадигму FP і чи існує в цьому випадку термін «анемічна модель».

Оновлення : нещодавно опубліковані книги та відео з цього питання.


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


1
"чи існує термін" анемічна модель "в такому випадку" Коротка відповідь, термін анемічної моделі був придуманий в контексті ОО. Говорячи про анемічну модель в контексті ПС взагалі немає сенсу. Можуть бути еквіваленти в сенсі опису того, що є ідіоматичним ПП, але це не має нічого спільного з анемічними моделями.
plalx

5
Еріка Еванса одного разу його запитали, що він говорить людям, які звинувачують його, що те, що він описує у своїй книзі, - це просто хороший об'єктно-орієнтований дизайн, і він відповів, що це не звинувачення, це правда, DDD - це просто хороше OOD, він просто написав спустити деякі рецепти та шаблони і дати їм назви, щоб легше було слідувати за ними та говорити про них. Тож не дивно, що DDD пов'язаний з OOD. Більш широким питанням буде, що таке перетини та відмінності між OOD та FPD, хоча вам слід спочатку визначити, що ви маєте на увазі під функціональним програмуванням.
Йорг W Міттаг

2
@ JörgWMittag: Ви маєте на увазі інше, ніж звичайне визначення? Існує безліч ілюстративних платформ, Haskell є найбільш очевидною.
Роберт Харві

Відповіді:


24

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

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

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

newtype UserID = UserID String

Це визначає UserIDфункцію , яка , коли задана Stringбудує значення, яке обробляє , якUserID система типу, але все ще тільки Stringпід час виконання. Тепер функції можуть оголосити, що вони вимагають UserIDзамість рядка; використання UserIDs, де ви раніше використовували захисні рядки проти коду, що об'єднує два UserIDs разом. Система типу гарантує, що цього не може відбутися, ніяких тестів не потрібно.

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

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

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

Для побудови UserIDсписку цифр необхідно надати. З огляду на це визначення, неправдиво показати, що UserIDіснування неможливо, яке не може бути представлене в URL-адресі. Визначення подібних моделей даних у Haskell часто допомагає розширеним функціям системи типу, таким як Data Data та General Algebraic Data Types (GADT) , які дозволяють системі типів визначати та доводити більше інваріантів щодо вашого коду. Коли дані відокремлюються від поведінки, визначення даних є єдиним засобом, який потрібно застосовувати до поведінки.


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

2
Гарна промова, але жодної відповіді на питання ОП.
SerG

10

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

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

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


2
"Значною мірою незмінність робить непотрібним тісно поєднувати свої функції з вашими даними, як захисники ООП".: Ще одна причина з’єднання даних і процедур - це реалізація поліморфізму, хоча динамічна відправка.
Джорджіо

2
Основна перевага зв’язку поведінки з даними в контексті DDD - це надання значущого бізнес-інтерфейсу; це завжди завжди під рукою. У нас є природний спосіб самодокументування коду (принаймні, до того, до чого я звик), і це запорука успішної комунікації з бізнес-експертами. Як тоді це здійснюється в ПП? Можливо, трубопровід допомагає, але що ще? Чи не родова природа FP не ускладнює ділові вимоги, щоб повернути інженера з коду?
Павло Воронін

7

При використанні DDD в OOP однією з основних причин розміщення бізнес-логіки в самих об'єктах домену є те, що бізнес-логіка зазвичай застосовується шляхом мутації стану об'єкта. Це пов’язано з інкапсуляцією: Employee.RaiseSalaryможливо, вимкнено salaryполе Employeeекземпляра, яке не повинно бути публічно встановленим.

У FP уникнути мутації, тому ви реалізуєте цю поведінку, створивши RaiseSalaryфункцію, яка приймає наявний Employeeекземпляр і повертає новий Employee екземпляр з новою зарплатою. Тож жодна мутація не бере участь: лише читання з оригінального об'єкта та створення нового об’єкта. З цієї причини таку RaiseSalaryфункцію не потрібно визначати як метод на Employeeкласі, але вона може жити де завгодно.

У цьому випадку стає природним відокремлення даних від поведінки: одна структура являє собою Employeeдані як повністю (анемічні), тоді як один (або кілька) модулів містять функції, які працюють на цих даних (зберігаючи незмінність).

Зауважте, що при поєднанні даних та поведінки, як у DDD, ви, як правило, порушуєте Принцип єдиної відповідальності (SRP): Employeeможливо, доведеться змінитись, якщо змінити правила зміни зарплат; але це може також знадобитися змінити, якщо змінити правила обчислення бонусу EOY. З підключеним підходом це не так, оскільки ви можете мати декілька модулів, кожен з яких несе відповідальність.

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


-1

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

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


5
Так, це в основному те, що говорить ОП у своєму питанні. Ви, здається, обидва пропустили те, що ви все ще можете мати функціональну композицію.
Роберт Харві

-3

Я хотів би знати, як DDD вписується в парадигму FP

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

і чи все ще існує термін «анемічна модель».

Ну, якщо ви маєте на увазі "аналогічно традиційному OOP", це допомагає ігнорувати звичні деталі реалізації та повернутися до основ: Якою мовою користуються ваші доменні експерти? З якою метою Ви захоплюєте своїх користувачів?

Припустимо, що вони говорять про зв'язування процесів і функцій разом, то, здається, функції (або, принаймні, "виконайте" об'єкти) в основному є вашими доменними об'єктами!

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


1
Анемічна модель виникає, коли ви передаєте абстрактні типи даних навколо, наприклад кортежі, записи чи списки, до різних функцій для обробки. Вам не потрібно нічого екзотичного, як "функція, яка не виконується" (що б там не було).
Роберт Харві

Звідси лапки навколо "функцій", щоб підкреслити, наскільки неналежним стає ярлик, коли вони анемічні.
Дарієн

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