Должен ли я отображать DTO в / из доменного объекта на стороне клиента и сервера? - PullRequest
16 голосов
/ 04 февраля 2010

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

Мой клиент общается с сервером только через WCF.

Таким образом, для каждого объекта домена у меня есть соответствующий DTO - простое представление, которое содержит только данные, - а также класс сопоставления.который реализует DtoMapper<DTO,Entity> и может преобразовывать объект в его эквивалент DTO или наоборот через статический шлюз:

var employee = Map<Employee>.from_dto<EmployeeDto>();

На стороне сервера этого приложения в основном речь идет о постоянстве, откуда мои DTO приходят изСлужба WCF десериализуется, а затем произвольный ORM сохраняет их в базе данных, или запрос WCF поступает из WCF, и ORM выполняет этот запрос к БД и возвращает объекты, которые должны быть сериализованы и отправлены обратно WCF.

Учитывая этот сценарий, имеет ли смысл сопоставлять мое хранилище ссущности домена, или я должен просто сопоставить напрямую с DTO?

Если я использую сущности домена, поток будет

  1. клиент запрашивает объект
  2. WCF передает запрос на сервер
  3. ORM запрашивает базу данных и возвращает доменные сущности
  4. доменные сущности, преобразованные в DTO с помощью преобразователя
  5. WCF сериализует DTO и возвращает клиенту
  6. клиент десериализует DTO
  7. DTO, преобразованный в сущность домена с помощью маппера
  8. созданных моделей представления, и т. д.

аналогично обратному маршруту

Если я сопоставлю прямо с DTO, я могу исключить одно сопоставление на объект для каждого запроса.Что я теряю при этом?

Единственное, что приходит на ум, - это еще одна возможность проверки перед вставкой / обновлением, потому что я не могу гарантировать, что DTO когда-либо подвергался проверке или даже существовалкак сущность домена перед отправкой по сети, и я предполагаю возможность проверки при выборе (если другой процесс мог бы поместить недопустимые значения в базу данных).Есть ли другие причины?Достаточно ли этих причин, чтобы оправдать дополнительные шаги сопоставления?

edit:

Я сказал «произвольный ORM» выше, и я хочу, чтобы вещи были такими же независимыми от ORM и постоянства, каквозможно, но если вам нужно добавить что-то особенное, специфичное для NHibernate, обязательно сделайте.

Ответы [ 6 ]

25 голосов
/ 13 февраля 2010

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

Рассмотрим, что такое веб-сервис. Это не просто абстракция над вашим ORM; это контракт . Это публичный API для ваших клиентов, как внутренних, так и внешних.

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

На самом деле это обычная практика (я не буду оскорблять читателей фразой «лучшая практика») по определенной причине создавать конкретные классы Request и Response для каждого сообщения; становится намного проще расширять возможности существующих служб и методов, не нарушая их изменений.

Клиенты, вероятно, не хотят точно такую ​​же модель, которую вы используете внутри службы. Если вы являетесь вашим единственным клиентом, то, возможно, это кажется прозрачным, но если у вас есть внешние клиенты и вы видели, насколько часто они могут отличаться от вашей интерпретации вашей системы, тогда вы поймете, как важно не допустить утечки вашей идеальной модели. за пределы сервисного API.


А иногда даже невозможно отправить вашу модель обратно через API. Есть много причин, почему это может произойти:

  • Циклы в графе объектов. Прекрасно в ООП; катастрофический в сериализации. Вам в конечном итоге приходится делать болезненный постоянный выбор того, в каком «направлении» граф должен быть сериализован. С другой стороны, если вы используете DTO, вы можете сериализовать в любом направлении, в котором хотите, в зависимости от того, какая задача под рукой.

  • Попытка использовать определенные типы механизмов наследования через SOAP / REST в лучшем случае может быть неудачей. Сериализатор XML старого стиля, по крайней мере, поддерживает xs:choice; DataContract нет, и я не буду придираться к обоснованию, но достаточно сказать, что у вас, вероятно, есть некоторый полиморфизм в вашей модели богатых доменов, и его почти невозможно передать через веб-сервис.

  • Ленивая / отложенная загрузка, которую вы, вероятно, используете, если используете ORM. Достаточно неловко убедиться, что сериализовано правильно - например, с использованием сущностей Linq to SQL, WCF даже не запускает ленивый загрузчик, он просто помещает null в это поле, если вы не загрузите его вручную - но проблема возникает еще хуже для данных, возвращаемых. Что-то простое, например, авто-свойство List<T>, которое инициализируется в конструкторе - достаточно часто встречается в модели предметной области - просто не работает в WCF, потому что оно не вызывает ваш конструктор. Вместо этого вам нужно добавить [OnDeserializing] метод инициализатора, и вы действительно не хотите загромождать модель вашего домена этим мусором.

  • Я также заметил в скобках, что вы используете NHibernate. Учтите, что такие интерфейсы, как IList<T>, вообще не могут быть сериализованы через веб-сервис! Если вы используете классы POCO с NHibernate, как и большинство из нас, то это просто не будет работать, точка.


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

  • Информация об учетной записи (номер счета, имя и т. Д.)
  • Данные, относящиеся к счету (номер счета, дата, срок оплаты и т. Д.)
  • Информация уровня A / R (предыдущий баланс, просроченные платежи, новый баланс)
  • Информация о продукте или услуге для всего, что указано в счете;
  • Etc.

Это, вероятно, хорошо вписывается в модель предметной области. Но что, если клиент хочет запустить отчет, который показывает 1200 из этих счетов? Какой-то отчет о сверке?

Это отстой для сериализации. Теперь вы отправляете 1200 счетов с одними и теми же сериализованными данными - одни и те же счета, одни и те же продукты, один и тот же A / R. Внутренне ваше приложение отслеживает все ссылки; он знает, что Счет-фактура № 35 и Счет-фактура № 45 предназначены для одного и того же клиента, и поэтому имеют общую ссылку Customer; вся эта информация теряется при сериализации, и в итоге вы отправляете смешное количество избыточных данных.

То, что вы действительно хотите, это отправить специальный отчет, который включает в себя:

  • Все учетные записи, включенные в отчет, и их A / R;
  • Все продукты включены в отчет;
  • Все счета-фактуры, только с идентификаторами продуктов и учетных записей.

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

Я надеюсь, что этих примеров достаточно, чтобы убедить вас сохранить нетронутыми ваши сопоставления из Контракта на обслуживание домена <->. Вы до сих пор делали абсолютно правильные вещи, у вас отличный дизайн, и было бы стыдно отрицать все эти усилия в пользу чего-то, что впоследствии может привести к серьезным головным болям.

2 голосов
/ 13 февраля 2010

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

Сказав, что я не уверен, где дополнительное сопоставлениеявляется?Вы извлекаете данные, используя ваш ORM (он же доменные сущности), и вы отображаете эти объекты в DTO, так что там только 1 сопоставление?Кстати, если вы еще не используете что-то вроде Automapper , чтобы сделать утомительное отображение для вас.

Эти те же DTO затем десериализуются на клиенте, и оттуда вы можете отобразить непосредственно на ваши UIViewModels,Таким образом, общая картина выглядит примерно так:

  • Клиент запрашивает сущность по идентификатору из службы WCF
  • Служба WCF получает сущность из репозитория / ORM
  • Использование AutoMapper для сопоставленияот объекта к DTO
  • Клиент получает DTO
  • Использование AutoMapper для сопоставления с пользовательским интерфейсом ViewModel
  • UIViewModel привязан к GUI
2 голосов
/ 10 февраля 2010

Вам все равно потребуется сопоставить DTO на стороне клиента, поэтому для симметрии лучше выполнить обратное отображение на стороне сервера. Таким образом вы изолируете свои преобразования в хорошо разделенные слои абстракции.

Слои абстракции хороши не только для валидации, но и для того, чтобы изолировать ваш код от изменений ниже / выше него и сделать ваш код более тестируемым и с меньшим количеством повторений.

Кроме того, если вы не заметили узкого места в производительности при дополнительной конверсии, помните: ранняя оптимизация - корень всех зол. :)

1 голос
/ 13 февраля 2010

У нас есть похожее приложение, в котором служба WCF выступает главным образом как шлюз для постоянного хранилища данных.

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

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

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

На стороне клиента, возможно, нет необходимости делать дополнительное сопоставление, если вы можете просто украсить или дополнить свои DTO, и это довольно простое решение.

1 голос
/ 11 февраля 2010

Когда вы говорите, что ваше серверное приложение "в основном" связано с постоянством, я думаю, что это ключевой момент, о котором стоит подумать. Действительно ли существует модель домена на стороне сервера, которая требует некоторого интеллекта в отношении данных, которые она получает, или ваша служба WCF просто выступает в качестве шлюза между моделью вашего домена и хранилищем данных?

Кроме того, подумайте, предназначен ли ваш DTO для клиентского домена.
Это единственный клиентский домен, которому нужен доступ к этому хранилищу данных через ваш сервис?
Являются ли серверные DTO гибкими или достаточно грубыми для обслуживания другого домена приложения?
Если нет, то, вероятно, стоит приложить усилия к тому, чтобы реализации внешнего интерфейса были абстрагированы.

(DB-> ORM-> EmployeeEntity-> Client1DTOAssembler-> Client1EmployeeDTO).

0 голосов
/ 13 февраля 2010

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

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

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

...