Можно ли говорить об анемичной модели в контексте функционального программирования?

40

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

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

Обновление : недавно опубликованная книга и видео на эту тему.

Павел Воронин
источник
1
Если вы говорите то, что, я думаю, вы говорите здесь, DTO - это анемичные, но первоклассные объекты в DDD, и существует естественное разделение между DTO и сервисами, которые их обрабатывают. Я согласен в принципе. Так же и этот пост в блоге , по-видимому.
Роберт Харви
2
Сильно связаны, если не прямой дубликат: почему модель анемичной области считается плохой в ООП, но очень важной в FP?
Роберт Харви
1
«существует ли в этом случае термин« анемичная модель »? Краткий ответ: термин« анемичная модель »был придуман в контексте ОО. Говорить об анемичной модели в контексте FP вообще не имеет смысла. Могут быть эквиваленты в смысле описания того, что является идиоматическим FP, но это не имеет ничего общего с анемичными моделями.
plalx
5
Эрика Эванса однажды спросили, что он говорит людям, которые обвиняют его в том, что то, что он описывает в своей книге, является просто хорошим объектно-ориентированным дизайном, и он ответил, что это не обвинение, это правда, DDD - просто хорошая ООД, он просто написал записать некоторые рецепты и шаблоны и дать им имена, чтобы было легче следовать им и говорить о них. Поэтому неудивительно, что DDD связан с OOD. Более широкий вопрос заключается в том, каковы пересечения и различия между OOD и FPD, хотя вам придется сначала определить, что вы подразумеваете под «функциональным программированием».
Йорг W Mittag
2
@ JörgWMittag: Вы имеете в виду, кроме обычного определения? Существует множество иллюстративных платформ, наиболее очевидной из которых является Haskell.
Роберт Харви

Ответы:

24

То, как описывается проблема «анемичной модели», не очень хорошо подходит для FP. Сначала это должно быть соответствующим образом обобщено. По сути, анемичная модель - это модель, которая содержит знания о том, как правильно ее использовать, которая не инкапсулирована самой моделью. Вместо этого эти знания распространяются вокруг кучи сопутствующих услуг. Эти услуги должны быть только клиентами модели, но из-за ее анемии они несут ответственность за это. Например, рассмотрим Accountкласс, который нельзя использовать для активации или деактивации учетных записей или даже поиска информации об учетной записи, если он не обрабатывается с помощью AccountManagerкласса. Учетная запись должна отвечать за основные операции над ней, а не за некоторый класс внешнего менеджера.

В функциональном программировании подобная проблема существует, когда типы данных не точно представляют то, что они должны моделировать. Предположим, нам нужно определить тип, представляющий идентификаторы пользователей. «Анемичное» определение будет означать, что идентификаторы пользователей являются строками. Это технически осуществимо, но сталкивается с огромными проблемами, потому что идентификаторы пользователей не используются как произвольные строки. Не имеет смысла объединять их или выделять из них подстроки, Unicode на самом деле не должен иметь значения, и они должны легко встраиваться в URL-адреса и другие контексты со строгими символьными и форматными ограничениями.

Решение этой проблемы обычно происходит в несколько этапов. Первый простой способ - сказать: «Ну, a UserIDпредставляется эквивалентно строке, но это разные типы, и вы не можете использовать один там, где ожидаете другого». Haskell (и некоторые другие типизированные функциональные языки) предоставляет эту функцию через newtype:

newtype UserID = UserID String

Это определяет UserIDфункцию, которая при задании Stringконструирует значение, которое обрабатываетсяUserID системой типов как a , но которое все еще просто Stringво время выполнения. Теперь функции могут объявить, что им требуется UserIDстрока вместо; используя UserIDs там, где вы ранее использовали строки, защищает от кода, объединяющего два UserIDs вместе. Система типов гарантирует, что этого не произойдет, тесты не требуются.

Слабость здесь в том, что код все еще может взять любой произвольный Stringподобный "hello"и построить UserIDиз него. Дальнейшие шаги включают создание функции «умного конструктора», которая при задании строки проверяет некоторые инварианты и возвращает только, UserIDесли они удовлетворены. Затем «тупой» UserIDконструктор становится закрытым, поэтому, если клиент хочет, UserIDон должен использовать умный конструктор, тем самым предотвращая появление искаженных идентификаторов пользователя.

Даже дальнейшие шаги определяют UserIDтип данных таким образом, что невозможно создать неправильный или «неправильный» тип данных просто по определению. Например, определяя UserIDкак список цифр:

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

Для UserIDсоставления списка цифр необходимо предоставить. Учитывая это определение, тривиально показать, что невозможно UserIDсуществование, которое не может быть представлено в URL. При определении таких моделей данных в Haskell часто используются расширенные функции системы типов, такие как типы данных и обобщенные алгебраические типы данных (GADT) , которые позволяют системе типов определять и доказывать больше инвариантов вашего кода. Когда данные не связаны с поведением, ваше определение данных - единственное средство, которое вам нужно для обеспечения поведения.

Джек
источник
2
А как насчет агрегатов и агрегатных корней, охраняющих инварианты, последние также могут быть выражены и легко поняты разработчиками впоследствии? Для меня наиболее ценная часть DDD - это прямое отображение бизнес-модели в коде. И вы отвечаете именно об этом.
Павел Воронин
2
Хорошая речь, но нет ответа на вопрос ОП.
SerG
10

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

Тем не менее, лучший способ сделать это сравнение, вероятно , смотреть на какие функции вы направляем к модели слоя по сравнению с услугами слоя . Хотя это не выглядит так же, как в ООП, в FP довольно распространенная ошибка - пытаться втиснуть несколько уровней абстракции в одну функцию.

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

Карл Билефельдт
источник
2
«В значительной степени неизменность делает ненужным тесное соединение ваших функций с вашими данными, как того требует ООП». Еще одна причина для объединения данных и процедур заключается в реализации полиморфизма через динамическую диспетчеризацию.
Джорджио
2
Основным преимуществом взаимодействия поведения с данными в контексте DDD является обеспечение значимого бизнес-интерфейса; это всегда под рукой. У нас есть естественный способ самодокументирования кода (по крайней мере, это то, к чему я привык), и это ключ к успешному общению с бизнес-экспертами. Как же это достигается в FP? Вероятно, помогает трубопровод, но что еще? Разве общая природа FP не усложняет бизнес-требования для обратного проектирования кода?
Павел Воронин
7

При использовании DDD в ООП одной из основных причин размещения бизнес-логики в самих объектах домена является то, что бизнес-логика обычно применяется путем изменения состояния объекта. Это связано с инкапсуляцией: Employee.RaiseSalaryвозможно, мутирует salaryполе Employeeэкземпляра, которое не должно быть общедоступным.

В FP мутации избегают, поэтому вы реализуете это поведение, создавая RaiseSalaryфункцию, которая принимает существующий Employeeэкземпляр и возвращает новый Employee экземпляр с новой зарплатой. Так что никакой мутации не происходит: только чтение из исходного объекта и создание нового объекта. По этой причине такую RaiseSalaryфункцию не нужно определять как метод Employeeкласса, но она может жить где угодно.

В этом случае становится естественным отделить данные от поведения: одна структура представляет Employeeданные (полностью анемичные), а один (или несколько) модулей содержат функции, которые работают с этими данными (сохраняя неизменность).

Обратите внимание, что когда вы объединяете данные и поведение, как в DDD, вы, как правило, нарушаете принцип единой ответственности (SRP): Employeeвозможно, придется измениться, если изменятся правила изменения заработной платы; но может также потребоваться изменить, если изменятся правила расчета бонуса EOY. В случае несвязанного подхода это не так, поскольку у вас может быть несколько модулей, каждый с одной ответственностью.

Итак, как обычно, подход FP обеспечивает большую модульность / компоновку.

ла-юмба
источник
-1

Я думаю, что суть дела в том, что анемичная модель со всей доменной логикой в ​​сервисах, которые работают на модели, в основном является процедурным программированием - в отличие от «реального» ОО-программирования, где у вас есть «умные» объекты, которые содержат не только данные но также и логика, которая наиболее тесно связана с данными.

И такой же контраст существует с функциональным программированием: «реальный» FP означает использование функций в качестве первоклассных сущностей, которые передаются как параметры, а также создаются на лету и возвращаются в качестве возвращаемого значения. Но когда вы не в состоянии использовать всю эту мощь и имеете только функции, которые работают со структурами данных, которые передаются между ними, тогда вы в конечном итоге окажетесь в одном месте: вы в основном выполняете процедурное программирование.

Майкл Боргвардт
источник
5
Да, это в основном то, что ОП говорит в своем вопросе. Вы оба, кажется, упустили момент, что у вас все еще может быть функциональная композиция.
Роберт Харви
-3

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

Я думаю, что это так, но в основном как тактический подход к переходу между неизменяемыми объектами значения или как способ запуска методов на объектах. (Где большая часть логики все еще живет в сущности.)

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

Что ж, если вы имеете в виду «способом, аналогичным традиционному ООП», то это помогает игнорировать обычные детали реализации и вернуться к основам: какой язык используют ваши доменные эксперты? Какое намерение вы захватываете от своих пользователей?

Предположим, что они говорят о связывании процессов и функций вместе, тогда кажется, что функции (или, по крайней мере, объекты «do-er») в основном являются вашими предметными объектами!

Таким образом, в этом сценарии «анемичная модель», вероятно, возникнет, когда ваши «функции» на самом деле не являются исполняемыми, а представляют собой просто совокупности метаданных, которые интерпретируются службой, которая выполняет реальную работу.

Darien
источник
1
Анемичная модель возникает, когда вы передаете абстрактные типы данных, такие как кортежи, записи или списки, различным функциям для обработки. Вам не нужно ничего более экзотического, чем «функция, которая не выполняется» (что бы это ни было).
Роберт Харви
Отсюда кавычки вокруг «функций», чтобы подчеркнуть, насколько неприемлемым становится ярлык, когда они анемичны.
Дариен
Если вы иронизируете, это немного неуловимо.
Роберт Харви