Должен ли я сопоставить DTO с / из объекта домена как на стороне клиента, так и на стороне сервера?

StackOverflow https://stackoverflow.com/questions/2201150

Вопрос

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

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

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

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

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

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

Если бы я использовал объекты домена, поток был бы

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

то же самое и на обратном пути

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

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

Редактировать:

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

Это было полезно?

Решение

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

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

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

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

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


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

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

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

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

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


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

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

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

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

Чего вы действительно хотите, так это отправить пользовательский отчет, который включает:

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мне нравится упрощать его и вносить изменения по мере необходимости позже, стараться избегать преждевременной оптимизации и т.д.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top