Как избежать моделей анемичных доменов и поддерживать разделение проблем? - PullRequest
20 голосов
/ 23 октября 2008

Похоже, что решение сделать ваши объекты полностью осведомленными об их ролях в системе, и при этом избегать слишком большого числа зависимостей в модели домена от базы данных и уровней обслуживания?

Например: скажем, у меня есть сущность с историей ревизий и несколько «справочных таблиц», на которые ссылаются данные, у вашего сущностного объекта должны быть методы для получения деталей из некоторых справочных таблиц, будь то путем предоставления доступ к строкам таблицы поиска или делегирование методов им, но для этого необходимо, чтобы уровень базы данных считывал данные из этих строк. Кроме того, когда объект сохраняется, ему необходимо знать не только, как сохранить себя, но также сохранять записи в истории изменений. Необходимо ли передавать ссылки на десятки различных объектов уровня данных и служебных объектов на объект модели? Кажется, что это делает логику гораздо более сложной для понимания, чем просто передача тонких моделей туда-сюда для объектов слоя обслуживания, но я слышал, что многие «мудрецы» рекомендуют такую ​​структуру.

Ответы [ 4 ]

19 голосов
/ 23 октября 2008

Действительно хороший вопрос. Я потратил немало времени на размышления на такие темы.

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

Вот мой взгляд на тему.

Модель предметной области анемична, поскольку не содержит логики предметной области. Другие объекты получают и устанавливают данные, используя объект анемичного домена. То, что вы описываете, не звучит для меня как логика предметной области. Это могут быть, но, как правило, справочные таблицы и другой технический язык, скорее всего, термины, которые что-то значат для нас, но не обязательно что-то для клиентов. Если это неверно, уточните.

В любом случае, создание и сохранение объектов домена не должны содержаться в самих объектах домена, потому что это не логика домена.

Таким образом, чтобы ответить на вопрос, нет, вам не следует вводить целую кучу недоменных объектов / концепций, таких как таблицы поиска и другие детали инфраструктуры. Это утечка одной заботы в другую. Шаблоны Factory и Repository из Domain-Driven Design лучше всего подходят для того, чтобы отделить эти проблемы от самой доменной модели.

Но учтите, что если у вас нет доменной логики, то в конечном итоге вы получите анемичные доменные объекты, то есть пакеты безмозглых получателей и установщиков, как это некоторые магазины утверждают, что делают SOA / уровни обслуживания .

Итак, как вы получаете лучшее из обоих миров? Как вы фокусируете свои доменные объекты только на доменной логике, сохраняя UI, конструкцию, постоянство и т. Д. В стороне? Я рекомендую вам использовать такую ​​технику, как Double Dispatch или какую-либо форму ограниченного доступа к методу .

Вот пример двойной отправки. Скажем, у вас есть эта строка кода:

entity.saveIn(repository);

В вашем вопросе saveIn () будет обладать всевозможными знаниями об уровне данных. Используя Double Dispatch, saveIn () делает это:

repository.saveEntity(this.foo, this.bar, this.baz);

А метод saveEntity () в хранилище обладает всеми знаниями о том, как сохранять данные на уровне данных, как и положено.

В дополнение к этой настройке вы можете иметь:

repository.save(entity);

, который просто звонит

entity.saveIn(this);

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

И да, вы могли бы, но IMO показывает слишком много того, как реализована ваша сущность, и эти средства доступа отвлекают от предметной логики. Я думаю, что единственный класс, который должен иметь get и sets - это класс, имя которого заканчивается на «Accessor».

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

ОК, я закончил. Как я уже сказал, я немного зациклен на этой теме.

1 голос
/ 11 декабря 2009

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

Там должно быть гораздо больше. то есть. Это анемично, потому что служба выполняет все бизнес-правила, а не сущность домена.

Service(IRepository) injected

Save(){

DomainEntity.DoSomething();
Repository.Save(DomainEntity);

}

'Do Something' is the business logic of the domain entity.

**This would be anemic**:
Service(IRepository) injected

Save(){

if(DomainEntity.IsSomething)
  DomainEntity.SetItProperty();
Repository.Save(DomainEntity);

}

Видите разницу в наследстве? Я делаю :)

1 голос
/ 23 октября 2008

«тонкие модели для объектов слоя обслуживания» - это то, что вы делаете, когда действительно хотите написать слой обслуживания.

ORM - это то, что вы делаете, когда не хотите писать служебный слой.

Когда вы работаете с ORM, вы все еще знаете, что навигация может включать запрос, но вы не останавливаетесь на нем.

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

Однако, если существует более полная объектная модель, мы имеем первоклассные вещи вместо этих вырожденных значений поиска.

Например, у меня есть несколько бизнес-транзакций, которые имеют один из n разных «тарифных планов» - своего рода модель ценообразования. Прямо сейчас унаследованная реляционная база данных имеет тарифный план в виде справочной таблицы с кодом, некоторыми номерами цен и (иногда) описанием.

[Все знают коды - коды священны. Никто не уверен, какими должны быть правильные описания. Но они знают коды.]

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

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

0 голосов
/ 23 октября 2008

Попробуйте "Шаблон хранилища" и "Проектирование на основе домена". DDD предлагает определять определенные объекты как совокупные корни других объектов. Каждый агрегат инкапсулирован. Сущности "упорство невежественны". Весь связанный с персистентностью код помещается в объект репозитория, который управляет доступом к данным для объекта. Таким образом, вам не нужно смешивать код, связанный с постоянством, с вашей бизнес-логикой. Если вы заинтересованы в DDD, посмотрите книгу Эрика Эванса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...