Как обработать нарушения ограничений БД в пользовательском интерфейсе? - PullRequest
12 голосов
/ 22 февраля 2009

Мы реализуем большинство наших бизнес-правил в базе данных, используя хранимые процедуры.

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

Например, ошибка БД, такая как «Невозможно вставить строку с повторяющимся ключом», совпадает с бизнес-правилом «вы не можете иметь более одного Foo с одним именем». Но мы «реализовали» его в наиболее здравом смысле: как уникальное ограничение, которое выдает исключение при нарушении правила.

Другие правила, такие как «Вам разрешено только 100 Foos в день», не вызывают ошибок, скажем так, они изящно обрабатываются пользовательским кодом, таким как return empty dataset, который код приложения проверяет и передает обратно пользовательский интерфейс.

И в этом заключается загвоздка. Наш код пользовательского интерфейса выглядит следующим образом (это код веб-сервисов AJAX.NET, но подойдет любая инфраструктура ajax):

WebService.AddFoo("foo", onComplete, onError); // ajax call to web service

function onComplete(newFooId) {
    if(!newFooId) {
        alert('You reached your max number of Foos for the day')
        return
    }
    // update ui as normal here
}

function onError(e) {
    if(e.get_message().indexOf('duplicate key')) {
        alert('A Foo with that name already exists');
        return;
    }
    // REAL error handling code here
}

(В качестве примечания: я замечаю, что это именно то, что делает stackoverflow, когда вы слишком быстро отправляете комментарии: сервер генерирует ответ HTTP 500 и пользовательский интерфейс его перехватывает.)

Итак, вы видите, что мы обрабатываем нарушения бизнес-правил в двух местах, одно из которых (то есть уникальная ошибка containt) обрабатывается как особый случай кода, который должен обрабатывать real ошибки (не нарушения бизнес-правил), поскольку .NET распространяет исключения вплоть до обработчика onError().

Это неправильно. Мои варианты, я думаю:

  1. перехватить исключение «нарушение дублированного ключа» на уровне сервера приложений и преобразовать во все, что ожидает пользовательский интерфейс в качестве флага «нарушение бизнес-правил»,
  2. выгрузить ошибку (скажем, с "select name from Foo where name = @Name") и вернуть то, что сервер приложений ожидает в качестве флага "нарушено бизнес-правило",
  3. в том же значении, что и 2): использовать уникальное ограничение, встроенное в слой db, и вслепую insert into Foo, перехватывая любые исключения и преобразовывая их во все, что ожидает сервер приложений, поскольку флаг "нарушено бизнес-правило"
  4. вслепую insert into Foo (например, 3) и пусть это исключение распространяется на пользовательский интерфейс, плюс заставит сервер приложений вывести нарушения бизнес-правил как действительные Exceptions (вместо 1). Таким образом, ВСЕ ошибки обрабатываются в коде onError() (или аналогичном) слоя пользовательского интерфейса.

Что мне нравится в 2) и 3), так это то, что нарушения бизнес-правил "выбрасываются" , где они реализуются : в хранимых процессах. Что мне не нравится в 1) и 3), так это то, что я думаю , что они включают в себя глупые проверки, такие как "if error.IndexOf('duplicate key')", точно так же, как то, что в настоящее время находится в слое пользовательского интерфейса.

Редактировать : мне нравится 4), но большинство людей говорят, что использовать Exception s только в исключительных обстоятельствах.

Итак, как вы, элегантно, относитесь к распространению нарушений бизнес-правил вплоть до пользовательского интерфейса?

Ответы [ 6 ]

5 голосов
/ 22 февраля 2009

Мы не выполняем нашу бизнес-логику в базе данных, но у нас есть вся наша проверка на стороне сервера с низкоуровневыми операциями DB CRUD, отделенными от бизнес-логики более высокого уровня и кода контроллера.

Что мы пытаемся сделать внутренне, так это передать объект валидации с помощью таких функций, как Validation.addError(message,[fieldname]). Различные прикладные уровни добавляют свои результаты проверки к этому объекту, а затем мы вызываем Validation.toJson(), чтобы получить результат, который выглядит следующим образом:

{
    success:false,
    general_message:"You have reached your max number of Foos for the day",
    errors:{
        last_name:"This field is required",
        mrn:"Either SSN or MRN must be entered",
        zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible"
    }
}

Это может быть легко обработано на стороне клиента для отображения сообщений, относящихся к отдельным полям, а также общих сообщений.

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

3 голосов
/ 22 февраля 2009

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

Где я был? Правильно.

Я думаю, что комбинация 2 и 3 - это, вероятно, путь.

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

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

2 голосов
/ 22 февраля 2009

В защиту # 4, SQL Server имеет довольно упорядоченную иерархию предустановленных уровней серьезности ошибок. Поскольку, как вы указываете, хорошо обрабатывать ошибки там, где есть логика, я был бы склонен обрабатывать это по соглашению между SP и абстракцией UI, а не добавлять кучу дополнительных связей. Тем более что вы можете выдавать ошибки как со значением, так и со строкой.

1 голос
/ 23 февраля 2009

Вот как я делаю вещи, хотя это может быть не лучшим для вас:

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

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

Когда дело доходит до проверки, в javascript можно отследить самые легкие ошибки (форматирование, длины полей и т. Д.), Хотя, конечно, вы никогда не предполагаете, что эти проверки ошибок имели место. Эти ошибки также проверяются в более безопасном, более контролируемом мире серверного кода.

Бизнес-правила (например, «вы можете иметь только столько foos в день») проверяются в коде на стороне сервера, на уровне бизнес-объектов.

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

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

1 голос
/ 23 февраля 2009

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

RAISERROR может быть вызван с msg_id , серьезностью и состоянием, а также с набором аргументов ошибки. При использовании этого способа в базу данных должно быть введено сообщение с указанным msg_id с использованием системной хранимой процедуры sp_addmessage. Это msg_id может быть получено как свойство ErrorNumber в SqlException, которое будет вызываться в коде .NET, вызывающем хранимую процедуру. Затем пользовательский интерфейс может принять решение о том, какое сообщение или другую индикацию отображать.

Аргументы ошибок подставляются в получающееся сообщение об ошибке аналогично тому, как работает оператор printf в C. Однако, если вы хотите просто передать аргументы обратно в пользовательский интерфейс, чтобы пользовательский интерфейс мог решить, как чтобы использовать их, просто сделайте так, чтобы сообщения об ошибках не имели текста, только заменители аргументов. Одно сообщение может быть «% s» |% d »для передачи строкового аргумента (в кавычках) и числового аргумента. Код .NET может разделить их и использовать в пользовательском интерфейсе так, как вам нравится.

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

1 голос
/ 22 февраля 2009

Я видел множество приложений на основе Ajax, которые в режиме реального времени проверяли такие поля, как имя пользователя (чтобы узнать, существует ли он уже), как только пользователь покидает поле редактирования. Мне кажется, что лучше, чем выход в базу данных, вызывать исключение, основанное на ограничении базы данных - он более проактивен, поскольку у вас есть реальный процесс: получите значение, проверьте, является ли он действительным, покажите ошибку, если нет, разрешить продолжить, если нет ошибок. Таким образом, кажется, что вариант 2 является хорошим.

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