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

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

Мой клиент говорит на сервере только через WCF.

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

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

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

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

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

  • объект запросов клиента
  • WCF передает запрос серверу
  • ORM запрашивает базу данных и возвращает объекты домена
  • объекты домена, преобразованные в DTO с помощью mapper
  • WCF сериализует DTO и возвращает клиенту
  • клиент десериализует DTO
  • DTO преобразован в объект домена с помощью mapper
  • созданные модели и т.д.

похож на обратный ход

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

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

изменить:

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

Ответ 1

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

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

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

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

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


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

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

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

    /li >
  • Lazy/отложенная загрузка, которую вы, вероятно, используете, если используете 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; не имеет смысла иметь эту структуру в вашей модели домена, потому что ваша модель домена уже позаботится об увольнениях по-своему.

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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