Как спроектировать объекты передачи данных на уровне бизнес-логики
-
05-09-2019 - |
Вопрос
DTO
Я создаю веб-приложение, которое хотел бы масштабировать для многих пользователей.Кроме того, мне нужно предоставить функциональность доверенным третьим лицам через веб-службы.
Я использую LLBLGen для создания уровня доступа к данным (с использованием SQL Server 2008).Цель состоит в том, чтобы создать уровень бизнес-логики, который защищает веб-приложение от деталей DAL и, конечно же, обеспечивает дополнительный уровень проверки за пределами DAL.Кроме того, насколько я могу судить прямо сейчас, веб-служба по сути будет тонкой оболочкой над BLL.
Разумеется, DAL имеет собственный набор объектов сущностей, например CustomerEntity, ProductEntity и т. д.Однако я не хочу, чтобы уровень представления имел прямой доступ к этим объектам, поскольку они содержат методы, специфичные для DAL, а сборка специфична для DAL и так далее.Итак, идея состоит в том, чтобы создать объекты передачи данных (DTO).Идея состоит в том, что это будут, по сути, простые старые объекты C#/.NET, которые имеют все поля, скажем, CustomerEntity, которые на самом деле являются таблицей базы данных Customer, но ничего другого, за исключением, возможно, некоторых свойств IsChanged/IsDirty.Итак, будут CustomerDTO, ProductDTO и т. д.Я предполагаю, что они будут наследоваться от базового класса DTO.Я думаю, что смогу сгенерировать их с помощью какого-нибудь шаблона для LLBLGen, но пока не уверен в этом.
Итак, идея состоит в том, что BLL раскроет свою функциональность, принимая и возвращая эти объекты DTO.Я думаю, что веб-служба будет обрабатывать преобразование этих объектов в XML для третьих сторон, использующих ее, многие из них могут не использовать .NET (кроме того, некоторые вещи будут вызываться скриптами из вызовов AJAX в веб-приложении с использованием JSON).
Я не уверен, как лучше всего это спроектировать и как именно двигаться дальше.Вот некоторые проблемы:
1) Как это должно быть представлено клиентам (уровень представления и код веб-службы)
Я думал, что будет один публичный класс, который будет иметь эти методы, и каждый вызов будет атомарной операцией:
InsertDTO, UpdateDTO, DeleteDTO, GetProducts, GetProductByCustomer и т. д.
Затем клиенты просто вызывали эти методы и передавали соответствующие аргументы, обычно DTO.
Это хороший и работоспособный подход?
2) Что вернуть из этих методов?Очевидно, что методы типа Get/Fetch вернут DTO.А как насчет вставок?Частью подписи может быть:
InsertDTO(DTO dto)
Однако при вставке что должно быть возвращено?Я хочу получать уведомления об ошибках.Однако для некоторых таблиц я использую автоинкрементные первичные ключи (однако некоторые таблицы имеют естественные ключи, особенно «многие ко многим»).
Одним из вариантов, о котором я думал, был класс Result:
class Result
{
public Exception Error {get; set;}
public DTO AffectedObject {get; set;}
}
Таким образом, при вставке DTO получит набор свойств get ID (например, CustomerDTO.CustomerID), а затем вставит этот объект результата.Клиент узнает, произошла ли ошибка, если Result.Error != null, а затем узнает идентификатор из свойства Result.AffectedObject.
Это хороший подход?Одна из проблем заключается в том, что создается впечатление, что он передает туда и обратно много избыточных данных (когда это просто идентификатор).Я не думаю, что добавление свойства «int NewID» будет чистым, потому что некоторые вставки не будут иметь такого автоинкрементного ключа.Другая проблема заключается в том, что я не думаю, что веб-службы справятся с этим хорошо?Я считаю, что они просто вернут базовый DTO для AffectedObject в классе Result, а не производный DTO.Полагаю, я мог бы решить эту проблему, имея МНОГО различных типов объектов Result (возможно, полученных из базового Result и наследующих свойство Error), но это не кажется очень чистым.
Хорошо, я надеюсь, что это не слишком многословно, но я хочу внести ясность.
Решение
1:Это довольно стандартный подход, который хорошо подходит для реализации «репозитория» для лучшего подхода к модульному тестированию.
2:Исключения (которые, кстати, должны быть объявлены как «ошибки» на границе WCF) будут возникать автоматически.Вам не нужно решать это напрямую.Для данных существует три распространенных подхода:
- использовать
ref
по контракту (не очень красиво) - вернуть (обновленный) объект, т.е.
public DTO SomeOperation(DTO item);
- возвращать только обновленную идентификационную информацию (первичный ключ/метку времени/и т. д.)
Во всем этом есть одна особенность: для каждой операции не требуется использовать другой тип (сравните с вашим Result
class, который необходимо будет дублировать для каждого DTO).
Другие советы
Вопрос 1:Чтобы решить эту проблему, вы можете думать о своих составных типах контракта данных WCF как об DTO.Таким образом, ваш уровень пользовательского интерфейса имеет доступ только к свойствам DataMember DataContract.Ваши атомарные операции будут методами, предоставляемыми вашим интерфейсом WCF.
Вопрос 2:Настройте свои контракты данных ответа так, чтобы они возвращали новый пользовательский тип с вашими первичными ключами и т. д.WCF также можно настроить для возврата исключений в пользовательский интерфейс.