В последнее время я все более часто встречаюсь с мнением, что Data Transfer Object - это антипаттерн, позволяющий "хоть как-то выживать rich domain model" в суровом мире n-tier приложений и что при использовании анемичной модели эти источники лишней работы вообще не нужны. На деле же выходит, что это совсем не так.
Прежде чем продолжить, уточню способы организации бизнес-логики(поведения и данных) в упомянутых моделях:
Rich Domain Model - поведение распределяется по бизнес-правилам, правилам валидации, спецификациям, стратегиям, доменным сервисам, сущностям, объектам-значениям. Данные в основном хранятся в сущностях и объектах-значениях.
Anemic Domain Model - под этим типом, обычно понимается несколько вариаций организаций бизнес-логики. Классическая - которую сформулировал сам Фаулер, доменные объекты(присутствуют только сущности и объекты-значения, как наборы примитивных данных) не содержат поведения или содержат его недостаточно и основное его скопление находится в сервисах уровня приложения, что делает способ похожим на способы Transaction Script/Table Module, приводящими к процедурному подходу и подходящими лишь для простых приложений. Именно от этого сходства и остерегает нас добрый Фаулер, упоминая, что каждый шаблон призван организовывать свой определенный объем и определенную сложность логики приложения(хотя, формально определить эту величину сложно) .
Но у этого типа анемичной модели есть сразу бросающийся в глаза(вообщем-то Фаулер его и бросает) изъян : зачем помещать бизнес-логику в слой координации, слой сервисов уровня приложения? Многим сразу понятно, что это неправильно, что это нарушает границу логического слоя. Поэтому, появляется частично-анемичная модель, в ней, как правило, есть четкое разделение на доменные объекты содержащие только поведение (бизнес-правила, правила валидации, спецификации, стратегии, сервисы и т.п.) и на сущности и объекты-значения хранящие только данные, но не содержащие поведения).
Скорее всего Фаулер и этот способ назвал бы анемичным, вообщем-то как и Эванс в своей Big Blue Book подчеркивает некорректность данного подхода.
В данной заметке, я специально не хочу сравнивать степень успешности использования того или иного подхода(rich и anemic), поскольку буду это делать в последующих статьях. Здесь, мне хотелось бы начать с опровержения самого простого довода, который приводится в пользу анемичной модели: в анемичной модели не нужны объекты переноса данных.
Так вот, объекты переноса данных, помимо отсоединения и предоставления возможности сериализации данных, необходимы для передачи нескольких элеметов информации за один вызов, причем эти элементы в 90% случаев отличаются своей структурой, от на первый взгляд эквивалентных объектов домена, хранящих состояние(сущностей и объектов-значений).
Мне реально очень сложно найти пример, где необходимо использовать Domain Model в n-tier среде и не нужны Data Transfer Object'ы. Рассмотрим типичную форму "экстенсивно" развивающейся торговой информационной системы:
Даже если проектировать приложение, с расчетом на прототипы экранов пользовательского интерфейса(например на эту форму), вряд ли получится ее разумно реализовать, используя анемичную модель, без объектов передачи данных. И вообщем-то понятно, что даже, если не появится еще несколько типов клиентских приложений к нашей системе, то и в этом приложении обязательно будут придуманы "похожие" формы, использующие данные похожие по структуре на наши сущности, но обязательно отличающиеся от них.
Я допускаю возможность существования n-tier приложений, реализованных без объектов переноса данных, но это либо временное явление, характерное для не до конца сформированных требований, либо приложения со способом организации и инкапсуляции бизнес-логики отличным от способа Domain Model(и парадигмы DDD).
Поэтому, коллеги, используя анемичную модель, тоже не обойтись без объектов переноса данных(Data Transfer Object - DTO).
1) Относительно "частично-анемичной" модели - это определение не подходит. Из данной модели очень просто получить Rich Model линейным соединением релевантного поведения с данными. Элементарный рефакторинг - и у нас Rich Model. Другой вопрос, что когда данные и поведение понадобится разделять (из-за появления новых контекстов, зависимостей, усложнения системы итд) - это уже не будет так просто. Рефакторниг Rich Model -> "частично-анемичная" не линеен и сложен. "Частично-анемичная" это идеал к которому нужно стермиться, ибо это что ни по одному формальному критерию не уступает Rich Model, а по целому ряду превосходит. Давайте дадим корректные определения: Rich Model = Static Rich Model (хорошо отражает суть вещей в текущем состоянии, эволюция и расширение поведения затруднено), "Частично-анемичная" = Flexible Rich Model (как отражает суть вещей в текущем состоянии, так и нацелена на лучшую реакцию к расширению, эволюция и расширение поведения естественно поддерживаемы и просты).
ReplyDeleteЦитата из книги: "Domain-Driven Design Quickly"
"One of the first things we are taught about modeling is to read
the business specifications and look for nouns and verbs. The
nouns are converted to classes, while the verbs become methods.
This is a simplification, and will lead to a shallow model. All
models are lacking depth in the beginning, but we should
refactor the model toward deeper and deeper insight."
2) Относительно защиты DTO да еще и в контексте их использования в UI. Рекомендую автору ознакомиться с http://martinfowler.com/eaaDev/uiArchs.html, затем оттуда же по ссылкам почитать про Supervising Controller, Passive View, Presentation Model (что есть Model-View-ViewModel).
Например, реализуя Model-View-ViewModel паттерн на клиенте мы используем доменную сушность выставляя лишь то что нужно в CustomerViewModel, мы можем использовать богатые возможности Data Binding предоставляемые конкретной средой, прикрепляя ViewModel в качестве источника данных. Скрин приведенный в примере может быть элементарно реализован в Model-View-ViewModel. Зачем тут DTO? Почему не использовать паттерны с медиатором между моделью и представлением?
using System;
namespace Model_View_ViewModel
{
public class CustomerViewModel
{
private readonly CustomerData _customerData;
private readonly ICustomerRepository _customerRepository;
public string CustomerName
{
get
{
return _customerData.Name;
}
set
{
_customerData.Name = value;
}
}
public string CustomerSurname
{
get
{
return _customerData.Surname;
}
set
{
_customerData.Surname = value;
}
}
public string CustomerFullname
{
get
{
return String.Format("[MR] {0}", _customerData.Fullname);
}
}
public CustomerViewModel(CustomerData customerData, ICustomerRepository customerRepository)
{
_customerData = customerData;
_customerRepository = customerRepository;
}
public bool IsSaveButtonEnabled
{
get
{
return !String.IsNullOrEmpty(_customerData.Name)
|| !String.IsNullOrEmpty(_customerData.Surname);
}
}
public void Save()
{
if (IsSaveButtonEnabled)
_customerRepository.AddCustomer(_customerData);
}
}
}
>1.
ReplyDeleteВ данном посте не хотелось бы обсуждать углубляться в "anemic vs rich".
>2.
Вы видимо не внимательно прочитали, речь идет об n-tier приложениях. А в случае, который вы рассмотрели, не важно какой тип модели использовать.
Я достаточно внимательно прочел. Не важно какой тип приложений. Замените репозиторий на удаленный сервис в моем примере если удобно.
ReplyDeleteЛегкие объекты с данными я могу передать на клиента. ViewModel может представить их для View в каком угодно виде. Зачем, коллега, мне DTO? Я прекрасно обошелся без него, что противоречит вашему выводу, что мне без них не обойтись в моей Flexible Rich Model.
>Легкие объекты с данными я могу передать на клиента.
ReplyDeleteСогласен.
>Зачем, коллега, мне DTO? Я прекрасно обошелся >без него..
Вы прекрасно обошлись без них в своем примитивном примере, где у покупателя 2 свойства: Имя и Фамилия, вообще-то как и все кто приводит этот довод. Рассмотрите мою форму. И представьте, что после реализации клиентского приложения на ASP.NET, необходимо реализовать такие же на J2SE/... и допустим на javascript/Dojo, ну или похожую но другую форму в том ASP.NET приложении.
Прочитайте еще раз параграф выделенный курсивом.
ReplyDeleteАбзац курсивом прочитал, форму вашу рассмотрел. Я не понимаю в чем проблема передать граф легких доменных объектов - положим для вашей формы: Заказ с коллекцией его Items, покупателя заказа с адресом итд.
ReplyDeleteДалее вы приплетаете J2SE, javascript/Dojo - на других платформах/форматах как для моих легких объектов, так и для ваших DTO придется извращаться с их приемом.
Похожая форма в ASP.NET приложении? Нет проблем, можно повторно реюзать весь или часть моего ViewModel.
Пока я вам показал что без DTO могу сделать все что вы с DTO. Если нет разницы - зачем городить DTO? Ну вам то понятно - связанные данные и логика не позволяют это гибкости - другим-то зачем навязывать костыли ограничений выбранной вами модели?
Не нравится мой компактный пример - приведите свой длинный и докажите почему без DTO мне не обойтись или обойтись сложнее. Пока я ничего кроме нерелевантных предложений что-то представить не вижу.
>граф легких доменных объектов
ReplyDeleteЭто уже и есть DTO.
Но допустим, вы предполагаете, что все его составляющие это ваши "легкие доменные объекты". Так как же тогда присоединить к ним свойства, которые которые не имеют отношения к доменной сущности, а специфичны и вычисляются конкретно для определенного отображения(например тот же Item из моей формы), и совершенно не нужны будут на следующей форме, более того передачи их на другую форму может нарушать правила безопасности.
DTO подразумевает отдельный тип. Граф доменных объектов != DTO.
ReplyDelete"Так как же тогда присоединить к ним свойства, которые которые не имеют отношения к доменной сущности, а специфичны и вычисляются конкретно для определенного отображения?"
Вы сами ответили на свой вопрос в своем вопросе: свойства специфичные для определенного отображения пусть и вычисляет отображение-модель (ViewModel - CustomerViewModel класс в моем примере). Не вижу смысла размазывать логику специфичную для представления по клиенту и серверу (вы хотите форматировать данные для клиента на сервере, затем на клиенте их представлять). Можно отдать клиенту необходимые доменные данные и доверить их представление.
В другую ViewModel передавайте только то, что нужно ей для отображения и не нарушает правила безопасности. Не нужны ей секретные Items - не передавайте ей Items.
>DTO подразумевает отдельный тип. Граф доменных объектов != DTO.
ReplyDeleteИнтересно какой же у вас будет доменный объект для данной формы.
>Вы сами ответили...
Ну допустим я продублирую логику вычисления простых значений(например, цена со скидкой) во всех типах моих клиентских приложений, но что же делать, если поля должны быть вычислены на сервере? Например попадание заказа под график маркетинговых акций? И что делать если мне необходимо выгружать во внешнюю систему купленные опред. покупателем товары, но не хочу отдавать информацию о том какую скидку он получил на определенный товар.
"Интересно какой же у вас будет доменный объект для данной формы." - не объект, а граф. Какой будет я уже писал: Заказ с коллекцией его Items, покупателя заказа с адресом итд.
ReplyDelete"Ну допустим я продублирую логику вычисления простых значений(например, цена со скидкой) во всех типах моих клиентских приложений, но что же делать, если поля должны быть вычислены на сервере?" - вы продублируете, а я использую стратегию высчисления (у меня-то она отдельно от данных). Если поля должны быть вычислены на сервере - включите поля в модель (в сущность).
"И что делать если мне необходимо выгружать во внешнюю систему купленные опред. покупателем товары, но не хочу отдавать информацию о том какую скидку он получил на определенный товар."
Не выгружайте скидку.
Моя Flexible Rich Model не отрицает возможность использования DTO. Я использую DTO везде где по каким-то объективным причинам не могу или не хочу использовать легкие доменные сущности. Например когда в сущности слишком много данных, а клиенту нужна лишь пара полей. Но могу и должен - разные вещи. Я использую DTO там где они реально нужны. А вы везде - даже если Customer идеально подходит экранной форме или почти идеально, что замечу при хорошем дизайне системы происходит в 80% случаев: DTO мне будет не нужен. А вам будет нужен, ибо в сущности живет или например может жить в дальнейшем серверно-специфичная-секретная логика.
>- не объект, а граф. Какой будет я уже писал: Заказ с коллекцией его Items, покупателя заказа с адресом итд.
ReplyDeleteТакой граф не удовлетворит своего потребителя(по крайней мере для моей формы).
>вы продублируете, а я использую стратегию высчисления
Вы продублируете стратегию вычисления на всех клиентских платформах.
>Если поля должны быть вычислены на сервере
Это уже вряд ли можно будет назвать доменной моделью, если я буду включать в нее свойства необходимые клиентским приложениям.
>Не выгружайте скидку.
Без DTO не получается(даже если менять иерархию наследования).
>Моя Flexible Rich Model не отрицает возможность использования DTO.
Отлично.
> при хорошем дизайне системы происходит в 80% случаев: DTO мне будет не нужен.
Это то, о чем говорил Мартин: "Не используйте шаблон доменной модели в простых(CRUD) приложениях". Интересно, кстати, что в ваших приложениях динамичнее изменяется: экраны или домен? Если экраны, то вам приходится перестраивать механизм взаимодействия на использование DTO для этой формы(например при добавление элементов UI использующих вычисляемые сложные значения, не имеющих прямого отношения к сущности).
Если модель, то вам необходимо просматривать всю систему на предмет предотвращения отдачи во вне дополнительных данных из изменившихся сущностей.
>> при хорошем дизайне системы происходит в 80% случаев: DTO мне будет не нужен.
ReplyDelete>Это то, о чем говорил Мартин: "Не используйте шаблон доменной модели в простых(CRUD) приложениях".
Причем тут DTO? Фаулер говорит о шаблоне доменной модели и нецелесообразности ее использования в простых приложениях.
Я же говорю о том, что при грамотном дизайне модель отражает потребность основного клиента на 80%. То есть примерно в 80% случаев в конкретном приложении DTO не нужен - достаточно легкого объекта домена (достаточно мне - вам все равно городить DTO).
Давайте выведем сухой остаток - я использую DTO там где он нужен, а вы там где он нужен и там где он не нужен потому что не можете передать доменный объект.
>Причем тут DTO? Фаулер говорит о шаблоне доменной модели и нецелесообразности ее использования в простых приложениях.
ReplyDelete-Согласен, но я рассматриваю необходимость использования объектов передачи данных как один из формальных показателей сложности приложения.
>Я же говорю о том, что при грамотном дизайне модель отражает потребность основного клиента на 80%.
- Это абстрактная цифра, откуда вы ее взяли? Моя форма не попадает в эти 80%?
>Давайте выведем сухой остаток - я использую DTO там где он нужен, а вы там где он нужен и там где он не нужен потому что не можете передать доменный объект.
- Это не совсем так, я тоже могу использовать методики, позволяющие избегать использования DTO, но в итоге(как вашем случае) это будет влиять на модель, а это соответственно не правильно, когда различные клиентские приложения напрямую влияют на модель.
>я рассматриваю необходимость использования объектов передачи данных как один из формальных показателей сложности приложения.
ReplyDeleteА я рассматриваю необходимость использования объектов передачи данных как ограничение используемой вами архитектуры.
>Это абстрактная цифра, откуда вы ее взяли? Моя форма не попадает в эти 80%?
Форма попадает. А может и нет. Потому что она такая же абстрактная как цифра в 80%. Опишите модель формально, скажите что есть что на форме и я вас скажу либо почему у вас плохая модель, либо как можно обойтись без DTO.
> я тоже могу использовать методики, позволяющие избегать использования DTO, но в итоге(как вашем случае) это будет влиять на модель, а это соответственно не правильно, когда различные клиентские приложения напрямую влияют на модель
Что вы выкручиваетесь? Ничего у меня влиять на модель не будет. Вот представим у вас модель. У меня такая же точно, но с отделенными данными и поведением. Я смогу использовать гораздо меньше DTO чем вы. Аргументы?
Кстати не стоит постоянно делать упор на "различные клиентские приложения". Эта классичесий аргумент из умных книжек на 90% всех разрабатываемых приложений (в том числе сложных приложений) не распространяется. Хотите или нет клиент как правило один, редко один и нечто малое еще, что использует лишь часть модели, очень редко два и более. Под основного клиента модель как правило и заточена - не потому что кто-то ее специально меняет под клиента, а просто потому что клиент и есть основной модификатор и потребитель данных. Соответственно поля Кастомера на форме клиента это практически прямое отражение модели. Ваша на первый взгляд сложная форма не исключение. Если данная форма из основного клиента вашей модели, то модель очень близка к ней.
>А я рассматриваю необходимость использования объектов передачи данных как ограничение используемой вами архитектуры.
ReplyDelete-Мы не говорим об архитектуре, мы говорим об использовании разновидности шаблона проектирования. Но вернемся к нашей беседе, повторюсь, DTO - это не серилизованные доменные сущности. И если в вашей системе нужны DTO, вы не сможете использовать доменные сущности. DTO - так же носит ответственность контракта данных(по-мимо смотрите абазац курсивом) для взаимодействия с другими приложениями(у вас это доменные сущности, что нарушает SRP).
>Потому что она такая же абстрактная как цифра в 80%.
-Форма то как раз вполне конкретная.
>Вот представим у вас модель. У меня такая же точно, но с отделенными данными и поведением. Я смогу использовать гораздо меньше DTO чем вы. Аргументы?
В этой ситуации кол-во DTO использованных вами будет таким как и в моем случае.
>на 90% всех разрабатываемых приложений
Кем разрабатываемых, Вами?
>Под основного клиента модель как правило и заточена
Никогда не проектировал модель, под клиентское приложение. Ко мне прототипы экранов приходят на много позднее(если вообще приходят), чем функциональная спецификация.
Сущность модели не являтся контрактом данных для клиентских приложений, сущность описывает понятие предметной области, когда как объект переноса данных, данные необходимые потребителю, которые обычно отличаются структурой от объектов модели предметной области. И вдруг, если структуры совпадают, нет разницы какую модель вы используете anemic или rich.(Хотя это неправильно использовать доменные объекты как объекты переноса данных, см. объяснение выше).
Использование анемичной модели, не избавляет Вас от использования объектов переноса данных.
>вы не сможете использовать доменные сущности. DTO - так же носит ответственность контракта данных(по-мимо смотрите абазац курсивом) для взаимодействия с другими приложениями(у вас это доменные сущности, что нарушает SRP).
ReplyDeleteОпять "другими приложениями" - давайте говорить четко - основным клиентом. Так вот мои доменные сущности почти всегда могут выполнять роль контракта между сервером и основным клиентом. SRP это не нарушает, так как их потребности клиента не влияют на мои доменные сущности. Это выходит само собой что сущности удовлятворяют потребностям большинства клиентских форм - почему я уже объяснил - что клиент и есть основной модификатор и потребитель данных. Я не меняю сущность если клиенту что-то требуется, в большинстве случаев достаточно изменить ViewModel, а если не достаточно, то уж тогда DTO.
>-Форма то как раз вполне конкретная.
Что зря меряться у кого что конкретнее - дайте код модели, DTO, формы если есть.
В данный момент я разрабатываю крупную CRM систему и 95% потребностей клиента решены через легкие доменные сущности + ViewModels. Только в 5% я вынужден использовать DTO (в большинстве случаев в угоду производительности).
>>Вот представим у вас модель. У меня такая же точно, но с отделенными данными и поведением. Я смогу использовать гораздо меньше DTO чем вы. Аргументы?
>В этой ситуации кол-во DTO использованных вами будет таким как и в моем случае.
Не будет у меня такого же количества DTO. В данный момент я разрабатываю крупную CRM систему и 95% потребностей клиента решены через легкие доменные сущности + ViewModels. Только в 5% я вынужден использовать DTO (в большинстве случаев в угоду производительности). У вас же DTO будет 100% модели - зачем? Потому что ограничение вашей архитектуры. Меня беспокоит не количество классов, а то что всю эту прокладку из DTO нужно поддерживать и в 80% случаев менять когда меняется модель, тогда как у меня этого зоопарка поддержка нет, и изменения модели достаточно.
>Использование анемичной модели, не избавляет Вас от использования объектов переноса данных.
Использование Flexible Rich Model полностью не избавляет. Но я использую DTO только там где это необходимо - а вы везде. Хватит увиливать - пора признать, что если кроме того что у вас данные и логика соединены других причин для DTO нет, то я его не буду использовать, а вы будете.
>>на 90% всех разрабатываемых приложений
>Кем разрабатываемых, Вами?
Всеми. Признайтесь хотя бы себе честно.
Поймите, как вы не пытаетесь абстрагироваться от основного клиента слоем DTO - придумывая себе внешних врагов в лице других клиентов, вы делаете приложение сложнее, труднее поддерживаемым, менее усточивым к ошибкам.
>>Под основного клиента модель как правило и заточена
>Никогда не проектировал модель, под клиентское приложение. Ко мне прототипы экранов приходят на много позднее(если вообще приходят), чем функциональная спецификация.
Хех, идеализм. То что вы "не проектировал модель, под клиентское приложение" говорит о вашей незрелости как проектировщика.
Вы всегда проектируете модель под основного клиента, хотите или нет, просто видимо не догадываетесь. Поймите, модель (домен) не живет сама по себе. Клиент модели - вот кто двигатель ее развития, основной источник ее изменений, основная сила которая влияет на модель. Хорошая модель (домен) проектируется с учетом того, как работает бизнес, а бизнес работает так, как работают его основные участники (пользователи) на всех уровнях. Функциональная спецификация уже описывает будущий UI, это просто нужно увидеть и прогнозировать. Советую почитать про BDD.
Вы видимо видите в моих словах какую-то крайность - то что UI влияет на модель что-ли - это не так. Потребности конечных пользователей влияют на модель. Модель не разрабатывается делать что-то и отдавать результаты "каким-то разным клиентам" - модель разрабатывается решать задачи бизнеса и говорить на языке бизнеса который во многом определяется доменными экспертами (они же очень часто пользователи системы).
>Опять "другими приложениями" - давайте говорить четко - основным клиентом. Так вот мои доменные сущности почти всегда могут выполнять роль контракта между сервером и основным клиентом...
ReplyDelete-Повторюсь, если даже такое происходит(что не считаю корректным), то в моих сущностях находятся такие же данные как и ваших я могу спокойно сериализовать и передать по сети клиенту.
>Что зря меряться у кого что конкретнее - дайте код модели, DTO, формы если есть.
-Давайте оставим такой пример, до следующей заметки.
>Хватит увиливать - пора признать, что если кроме того что у вас данные и логика соединены других причин для DTO нет, то я его не буду использовать, а вы будете.
-Опять же, если рассматривать, что нет необходимости использовать DTO, я так же спокойно сериализую и отправлю данные из своих сущностей в сеть. НО ПОВТОРЮСЬ Я НИКОГДА не произвожу EXPOSING DOMAIN MODEL клиентским приложениям(у меня их в 80%-х :) несколько), для этого пользуюсь фасадами и т.п.
>Всеми. Признайтесь хотя бы себе честно.
- Я бы рад признаться себе в этом, но заказчики не дают.
>Хех, идеализм. То что вы "не проектировал модель, под клиентское приложение" говорит о вашей незрелости как проектировщика.
- Не хочу в комментариях к этому сообщению, обсуждать свою, вашу или чью-либо еще зрелость.
>Вы всегда проектируете...
Я очень долго разрабатывал большие приложения не имеющие ни какого пользовательского интерфейса.
Интерфейс не влияет на создание и изменение модели, как и BDD не имеет к нему никакого отношения.
Предлагаю, на этом остановится, и продолжить в следующем посте, но уже на немного другую тему(глобально тема не меняется: rich vs anemic).
Спасибо.