Проверка в многоуровневом приложении

Мне интересно, как лучше всего выполнить проверку ограничений базы данных (например, UNIQUE) в приложении ASP.NET MVC, построенном с учетом DDD, где базовыми уровнями являются уровень приложения (службы приложений), уровень домена (модель домена) и уровень инфраструктуры (логика постоянства, ведение журнала и т. д.).

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

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

Когда ошибка обнаружена, как бы вы передали ее в ASP.NET MVC, чтобы пользователь мог хорошо проинформироваться об ошибке? Предпочтительно использовать ModelStateDictionary, чтобы ошибка была легко выделена в форме.

В приложении N-Lyered от Microsoft Spain используется интерфейс IValidatableObject, а самая простая проверка свойств выполняется на самом объекте, например:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    var validationResults = new List<ValidationResult>();

    if (String.IsNullOrWhiteSpace(this.FirstName))
        validationResults.Add(new ValidationResult(Messages.validation_CustomerFirstNameCannotBeNull, new string[] { "FirstName" }));

    return validationResults;
}

Перед сохранением объекта вызывается сообщение Validate, чтобы убедиться, что свойства действительны:

void SaveCustomer(Customer customer)
{
    var validator = EntityValidatorFactory.CreateValidator();

    if (validator.IsValid(customer)) //if customer is valid
    {
        _customerRepository.Add(customer);
        _customerRepository.UnitOfWork.Commit();
    }
    else
        throw new ApplicationValidationErrorsException(validator.GetInvalidMessages<Customer>(customer));
}

Затем ApplicationValidationErrorsException может быть перехвачено в приложении MVC, а сообщения об ошибках проверки могут быть проанализированы и вставлены в файл ModelStateDictionary.

Я мог бы добавить всю логику проверки в метод SaveCustomer, например. запрос к базе данных, проверяющий, существует ли уже клиент, используя данный столбец (УНИКАЛЬНЫЙ). Может быть, это нормально, но я бы предпочел, чтобы validator.IsValid (или что-то подобное) делало это за меня, или чтобы проверка выполнялась еще раз на уровне инфраструктуры (если она относится сюда, я не уверен).

Как вы думаете? Как ты делаешь это? Мне очень интересно получить больше информации о различных методах проверки в многоуровневых приложениях.


Возможное решение №1

В случае, когда логика проверки не может быть реализована на уровне представления (как предлагает Юлиан Маргаринтеску) и должна выполняться на уровне обслуживания, как бы вы передавали ошибки проверки на уровень представления?

У Microsoft есть предложение здесь (см. листинг 5). Что вы думаете о таком подходе?


person Tommy Jakobsen    schedule 01.05.2012    source источник
comment
Я бы посоветовал вам прочитать обзор примера приложения, сделанный Айенде, он состоит из нескольких постов, начиная с здесь. На что автор примера приложения ответил здесь. Я пришел к выводу, что не стоит тратить слишком много времени на этот образец приложения.   -  person Marijn    schedule 02.05.2012
comment
Выбросьте весь этот технический беспорядок MS из окна. Начните с нуля, на чистом C#. Создайте простой проект библиотеки классов и попытайтесь смоделировать свой домен, как если бы не было необходимости сохранять что-либо в базе данных. Только тогда вы сможете на самом деле понять, какие фреймворки вам нужно добавить, чтобы они работали/выглядели хорошо, и какое негативное влияние они оказывают на вашу кодовую базу. Когда вы начинаете с большой архитектуры, не думая о предметной области, вы неизбежно увязнете в лабиринте бессмысленных головоломок.   -  person Arnis Lapsa    schedule 02.05.2012
comment
Я знаю это, и да, я использовал это слово, что на самом деле не так. Некоторые реализации, такие как единица работы и репозиторий, вдохновлены их образцом, но я далек от того, чтобы использовать все их компоненты. Мое приложение очень простое C#, за исключением Unity, Entity Framework (DbContext) и AutoMapper.   -  person Tommy Jakobsen    schedule 02.05.2012
comment
Я удалил свое упоминание об образце приложения MS, потому что это не имело отношения к вопросу.   -  person Tommy Jakobsen    schedule 02.05.2012


Ответы (2)


Вы упомянули DDD, но DDD — это гораздо больше, чем сущности и репозитории. Я предполагаю, что вы знакомы с книгой г-на Эрика Эванса «Дизайн, управляемый предметной областью», и я настоятельно рекомендую вам перечитать главы о стратегическом проектировании и ограниченных контекстах. Также у г-на Эванса есть очень хороший доклад под названием «Что я узнал о DDD со времени написания книги», который вы можете найти здесь< /а>. Доклады Грега Янга или Уди Дахана о SOA, CQRS и источниках событий также содержат много информации о DDD и применении DDD. Я должен предупредить вас, что вы можете обнаружить вещи, которые изменят ваше представление о применении DDD.

Теперь о вашем вопросе о проверке. Один из подходов может состоять в том, чтобы запросить базу данных (используя вызов Ajax, направленный в службу приложений), как только пользователь введет что-то в поле «имя», и попытаться предложить альтернативное имя, если тот, который он ввел, уже существует. Когда пользователь отправляет форму, попробуйте вставить запись в базу данных и обработать любое исключение с повторяющимся ключом (на уровне репозитория или службы приложения). Поскольку вы уже заранее проверяете дубликаты, случаи, когда вы получаете исключение, должны быть довольно редкими, поэтому любое приличное сообщение «Извините, пожалуйста, повторите попытку» должно сработать, поскольку, если у вас нет МНОГО пользователей, они, вероятно, никогда его не увидят. .

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

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

Я не уверен, что это ответит на ваши вопросы, но я надеюсь, что это поможет вам найти ответы самостоятельно.

person Iulian Margarintescu    schedule 02.05.2012
comment
Спасибо за ответ Лулиан. Я очень хорошо знаю, что DDD НАМНОГО больше, чем то, что я упомянул, и я много раз читал книгу Эванса и видел его выступление, на которое вы ссылаетесь, мой вопрос был больше о проверке и где ее выполнить . Я никогда не должен был упоминать DDD, поскольку люди склонны сходить с ума (не говорю, что вы это сделали) :-) Но я все еще учусь, и сейчас речь идет о логике проверки и о том, где ее разместить. У вас есть хорошие инструкции, и я посмотрю на сообщение, которое вы упомянули. Спасибо! - person Tommy Jakobsen; 02.05.2012
comment
Имя клиента - это простой пример, который может обрабатываться клиентом, предоставляя услугу, как вы упомянули. Но когда проверка становится более сложной и в случае, когда ее нельзя выполнить на уровне представления, как передать результаты проверки с уровня службы на уровень представления? У MS есть предложение здесь: asp.net/mvc/tutorials/older-versions/models-(data)/ (см. листинг 5). Что вы думаете о таком подходе? - person Tommy Jakobsen; 02.05.2012
comment
Лично мне такой подход не нравится, потому что он предполагает, что вы можете создать недопустимый продукт и полагаться на сервис для добавления дополнительной логики. Это также означает, что модель на самом деле является анемичной моделью предметной области, которая, если вы попытаетесь выполнить DDD, станет анти-шаблоном. Вы можете сделать так, чтобы уровень представления вызывал службу с DTO как можно скорее (но до фактического создания AR) с единственной целью применения логики проверки. Служба может напрямую возвращать результат проверки, а уровень представления может просто отображать его. - person Iulian Margarintescu; 03.05.2012
comment
Во время создания AR, если обнаружено недопустимое состояние, создайте исключение. Фактическая логика проверки может быть в общей сборке, если дублирование кода является проблемой. Также существует два типа проверки — проверка совокупных инвариантов Roos и базовая проверка пользовательского интерфейса. Вы не можете и не должны выполнять инвариантную проверку за пределами совокупного корня, поскольку он тесно связан с границами AR. Базовую проверку пользовательского интерфейса следует выполнить как можно скорее, чтобы предотвратить недопустимое состояние. Надеюсь, это имеет смысл. - person Iulian Margarintescu; 03.05.2012

Мне интересно, как лучше всего проверить ограничения базы данных (например, UNIQUE)

и если выбрать это, должно ли это быть сделано в репозитории или это должна быть работа службы приложений?

Это зависит от того, что вы проверяете.

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

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

P.S. репозиторий - это сервис. Не смотрите на сервисы как на универсальное хранилище необходимого, но трудно назвать правильно кода. Именование имеет значение. То же самое и с такими именами, как Helpers, Managers, Common, Utilities и т. д. — они в значительной степени бессмысленны.

Кроме того, вам не нужно засорять кодовую базу именами шаблонов: AllProducts › ProductRepository; Регистратор Заказов › Служба Заказов; order.isCompleted › IsOrderCompletedSpecification.IsSatisfiedBy.

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

Я бы запросил базу данных. Хотя, если высокая производительность вызывает беспокойство, а доступность имени клиента - это единственное, что должна обеспечивать база данных, я бы предпочел полагаться на базу данных (на 1 круг туда и обратно).

Когда ошибка обнаружена, как бы вы передали ее в ASP.NET MVC, чтобы пользователь мог быть хорошо проинформирован об ошибке? Предпочтительно использовать ModelStateDictionary, чтобы ошибка была легко выделена в форме.

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

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

person Arnis Lapsa    schedule 03.05.2012