Большинство шаблонов тактического проектирования DDD относятся к объектно-ориентированной парадигме, а анемичная модель описывает ситуацию, когда вся бизнес-логика помещается в сервисы, а не в объекты, что делает их своего рода DTO. Другими словами, анемичная модель является синонимом процедурного стиля, который не рекомендуется для сложной модели.
Я не очень опытен в чисто функциональном программировании, но я хотел бы знать, как DDD вписывается в парадигму FP и существует ли в этом случае термин «анемичная модель».
Обновление : недавно опубликованная книга и видео на эту тему.
functional-programming
domain-driven-design
Павел Воронин
источник
источник
Ответы:
То, как описывается проблема «анемичной модели», не очень хорошо подходит для FP. Сначала это должно быть соответствующим образом обобщено. По сути, анемичная модель - это модель, которая содержит знания о том, как правильно ее использовать, которая не инкапсулирована самой моделью. Вместо этого эти знания распространяются вокруг кучи сопутствующих услуг. Эти услуги должны быть только клиентами модели, но из-за ее анемии они несут ответственность за это. Например, рассмотрим
Account
класс, который нельзя использовать для активации или деактивации учетных записей или даже поиска информации об учетной записи, если он не обрабатывается с помощьюAccountManager
класса. Учетная запись должна отвечать за основные операции над ней, а не за некоторый класс внешнего менеджера.В функциональном программировании подобная проблема существует, когда типы данных не точно представляют то, что они должны моделировать. Предположим, нам нужно определить тип, представляющий идентификаторы пользователей. «Анемичное» определение будет означать, что идентификаторы пользователей являются строками. Это технически осуществимо, но сталкивается с огромными проблемами, потому что идентификаторы пользователей не используются как произвольные строки. Не имеет смысла объединять их или выделять из них подстроки, Unicode на самом деле не должен иметь значения, и они должны легко встраиваться в URL-адреса и другие контексты со строгими символьными и форматными ограничениями.
Решение этой проблемы обычно происходит в несколько этапов. Первый простой способ - сказать: «Ну, a
UserID
представляется эквивалентно строке, но это разные типы, и вы не можете использовать один там, где ожидаете другого». Haskell (и некоторые другие типизированные функциональные языки) предоставляет эту функцию черезnewtype
:Это определяет
UserID
функцию, которая при заданииString
конструирует значение, которое обрабатываетсяUserID
системой типов как a , но которое все еще простоString
во время выполнения. Теперь функции могут объявить, что им требуетсяUserID
строка вместо; используяUserID
s там, где вы ранее использовали строки, защищает от кода, объединяющего дваUserID
s вместе. Система типов гарантирует, что этого не произойдет, тесты не требуются.Слабость здесь в том, что код все еще может взять любой произвольный
String
подобный"hello"
и построитьUserID
из него. Дальнейшие шаги включают создание функции «умного конструктора», которая при задании строки проверяет некоторые инварианты и возвращает только,UserID
если они удовлетворены. Затем «тупой»UserID
конструктор становится закрытым, поэтому, если клиент хочет,UserID
он должен использовать умный конструктор, тем самым предотвращая появление искаженных идентификаторов пользователя.Даже дальнейшие шаги определяют
UserID
тип данных таким образом, что невозможно создать неправильный или «неправильный» тип данных просто по определению. Например, определяяUserID
как список цифр:Для
UserID
составления списка цифр необходимо предоставить. Учитывая это определение, тривиально показать, что невозможноUserID
существование, которое не может быть представлено в URL. При определении таких моделей данных в Haskell часто используются расширенные функции системы типов, такие как типы данных и обобщенные алгебраические типы данных (GADT) , которые позволяют системе типов определять и доказывать больше инвариантов вашего кода. Когда данные не связаны с поведением, ваше определение данных - единственное средство, которое вам нужно для обеспечения поведения.источник
В значительной степени неизменность делает ненужным тесное соединение ваших функций с вашими данными, как сторонники ООП. Вы можете сделать столько копий, сколько захотите, даже создавая производные структуры данных, в коде, далеко удаленном от исходного кода, не опасаясь неожиданного изменения исходной структуры данных из-под вас.
Тем не менее, лучший способ сделать это сравнение, вероятно , смотреть на какие функции вы направляем к модели слоя по сравнению с услугами слоя . Хотя это не выглядит так же, как в ООП, в FP довольно распространенная ошибка - пытаться втиснуть несколько уровней абстракции в одну функцию.
Насколько я знаю, никто не называет это анемичной моделью, поскольку это термин ООП, но эффект тот же. Вы можете и должны повторно использовать универсальные функции везде, где это применимо, но для более сложных или специфичных для приложения операций вы также должны предоставить богатый набор функций только для работы с вашей моделью. Создание соответствующих слоев абстракции - хороший дизайн в любой парадигме.
источник
При использовании DDD в ООП одной из основных причин размещения бизнес-логики в самих объектах домена является то, что бизнес-логика обычно применяется путем изменения состояния объекта. Это связано с инкапсуляцией:
Employee.RaiseSalary
возможно, мутируетsalary
полеEmployee
экземпляра, которое не должно быть общедоступным.В FP мутации избегают, поэтому вы реализуете это поведение, создавая
RaiseSalary
функцию, которая принимает существующийEmployee
экземпляр и возвращает новыйEmployee
экземпляр с новой зарплатой. Так что никакой мутации не происходит: только чтение из исходного объекта и создание нового объекта. По этой причине такуюRaiseSalary
функцию не нужно определять как методEmployee
класса, но она может жить где угодно.В этом случае становится естественным отделить данные от поведения: одна структура представляет
Employee
данные (полностью анемичные), а один (или несколько) модулей содержат функции, которые работают с этими данными (сохраняя неизменность).Обратите внимание, что когда вы объединяете данные и поведение, как в DDD, вы, как правило, нарушаете принцип единой ответственности (SRP):
Employee
возможно, придется измениться, если изменятся правила изменения заработной платы; но может также потребоваться изменить, если изменятся правила расчета бонуса EOY. В случае несвязанного подхода это не так, поскольку у вас может быть несколько модулей, каждый с одной ответственностью.Итак, как обычно, подход FP обеспечивает большую модульность / компоновку.
источник
Я думаю, что суть дела в том, что анемичная модель со всей доменной логикой в сервисах, которые работают на модели, в основном является процедурным программированием - в отличие от «реального» ОО-программирования, где у вас есть «умные» объекты, которые содержат не только данные но также и логика, которая наиболее тесно связана с данными.
И такой же контраст существует с функциональным программированием: «реальный» FP означает использование функций в качестве первоклассных сущностей, которые передаются как параметры, а также создаются на лету и возвращаются в качестве возвращаемого значения. Но когда вы не в состоянии использовать всю эту мощь и имеете только функции, которые работают со структурами данных, которые передаются между ними, тогда вы в конечном итоге окажетесь в одном месте: вы в основном выполняете процедурное программирование.
источник
Я думаю, что это так, но в основном как тактический подход к переходу между неизменяемыми объектами значения или как способ запуска методов на объектах. (Где большая часть логики все еще живет в сущности.)
Что ж, если вы имеете в виду «способом, аналогичным традиционному ООП», то это помогает игнорировать обычные детали реализации и вернуться к основам: какой язык используют ваши доменные эксперты? Какое намерение вы захватываете от своих пользователей?
Предположим, что они говорят о связывании процессов и функций вместе, тогда кажется, что функции (или, по крайней мере, объекты «do-er») в основном являются вашими предметными объектами!
Таким образом, в этом сценарии «анемичная модель», вероятно, возникнет, когда ваши «функции» на самом деле не являются исполняемыми, а представляют собой просто совокупности метаданных, которые интерпретируются службой, которая выполняет реальную работу.
источник