2009-02-22 8 views
12

Implementamos la mayoría de nuestras reglas de negocio en la base de datos, utilizando procesos almacenados.¿Cómo manejar las violaciones de restricción db en la interfaz de usuario?

Nunca puedo decidir cómo pasar los errores de violación de restricción de datos de la base de datos a la interfaz de usuario. Las limitaciones de las que hablo están más ligadas a las reglas comerciales que a la integridad de los datos.

Por ejemplo, un error de db como "No se puede insertar una fila de clave duplicada" es lo mismo que la regla de negocios "no se puede tener más de un Foo con el mismo nombre". Pero lo hemos "implementado" en la ubicación con el sentido más común: como una restricción única que arroja una excepción cuando se infringe la regla.

Otras reglas como "Solo se permiten 100 Foos por día" no causan errores, por ejemplo, ya que se manejan con gracia mediante código personalizado como return empty dataset que el código de la aplicación busca y devuelve al ui capa.

Y ahí radica el problema. Nuestro código de interfaz de usuario se parece a esto (este es el código AJAX.NET servicios web, pero cualquier marco ajax hará):

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 
} 

(Como nota al margen: Me he dado cuenta que esto es lo que hace stackoverflow al momento de enviar los comentarios con demasiada rapidez: el servidor genera una respuesta HTTP 500 y el ui lo capta)

Como puede ver, estamos manejando infracciones de reglas comerciales en dos lugares, uno de los cuales (es decir, el error de constancia único) se maneja como un caso especial para el código que se supone que maneja errores reales (no infracciones de reglas comerciales), ya que .NET propaga Excepciones hasta el controlador onError().

Esto se siente mal. Mis opciones creo que son:

  1. captura la excepción 'duplicado violación clave' en el nivel de servidor de aplicaciones y convierten a lo que es la interfaz de usuario espera como la "regla de negocio violado" bandera,
  2. adelantar el error (digamos, con un "select name from Foo where name = @Name") y devolver lo que sea que el servidor de la aplicación espera como la bandera "regla comercial violada"
  3. en el mismo estadio que 2): aprovechar la restricción única incorporada en la capa db y ciegamente insert into Foo, atrapando cualquier excepción y convirtiéndola en lo que sea que sea la aplicación que se sirve r espera como la "regla de negocio violó" bandera
  4. ciegamente insert into Foo (como 3) y dejar que la excepción se propaga a la interfaz de usuario, además tienen las violaciónes de reglas de negocio de servidores de aplicaciones plantean como bienes Exceptions (en contraposición a 1). De esta forma, TODOS los errores se manejan en el código onError() (o similar) de la capa ui.

Lo que me gusta 2) y 3) es que las violaciónes de reglas de negocio son "arrojados" donde se implementan: en el procedimiento almacenado. Lo que no me gusta de 1) y 3) es que creo que implican controles estúpidos como "if error.IndexOf('duplicate key')", al igual que lo que está en la capa ui actualmente.

Editar: me gusta 4), pero la mayoría de la gente dice utilizar Exception s sólo en excepcionales circunstancias.

Entonces, ¿cómo manejan ustedes las personas que propagan las violaciones de reglas comerciales hasta la interfaz de usuario con elegancia?

Respuesta

1

Un procedimiento almacenado puede usar la instrucción RAISERROR para devolver información de error a la persona que llama. Esto se puede utilizar de una manera que permita a la interfaz del usuario decidir cómo aparecerá el error, al tiempo que permite que el procedimiento almacenado proporcione los detalles del error.

Se puede llamar a RAISERROR con msg_id, gravedad y estado, y con un conjunto de argumentos de error. Cuando se utiliza de esta manera, se debe haber ingresado un mensaje con el msg_id en la base de datos utilizando el procedimiento sp_addmessage system stored. Este msg_id se puede recuperar como la propiedad ErrorNumber en la SqlException que se generará en el código .NET que llama al procedimiento almacenado. La interfaz de usuario puede decidir qué tipo de mensaje u otra indicación mostrar.

El error argumentos se sustituyen en el mensaje de error resultante de manera similar a la forma en la declaración printf funciona en C. Sin embargo, si sólo quiere pasar los argumentos de nuevo a la interfaz de usuario para que la interfaz de usuario puede decidir cómo usarlos , simplemente haga que los mensajes de error no tengan texto, solo marcadores de posición para los argumentos. Un mensaje podría ser '"% s" |% d' para devolver un argumento de cadena (entre comillas) y un argumento numérico. El código .NET podría separarlos y usarlos en la interfaz de usuario como prefiera.

RAISERROR también se puede utilizar en un bloque TRY CATCH en el procedimiento almacenado. Eso le permitiría capturar el error de clave duplicada y reemplazarlo con su propio número de error que significa "clave duplicada al insertar" en su código, y puede incluir el valor real de la clave. Su UI podría usar esto para mostrar "Número de pedido ya existe", donde "x" fue el valor de clave proporcionado.

+0

Deseo que la persona que votó negativamente por esto y http://stackoverflow.com/questions/581994/-net-coding-standards-and-framework-for-a-web-service/582429#582429 den una razón. Dos respuestas muy diferentes, el mismo resultado, en un minuto. –

+1

El código que eventualmente llama a 'RAISERROR' todavía necesitará realizar una" verificación estúpida "como' CHARINDEX ('duplicate key', @errorMessage)> 0' y encontré esta pregunta porque mi escenario de ejemplo involucra * dos * clave única potencial Violaciones de restricciones y tenía curiosidad de saber si había alguna manera de determinar qué clave se había violado sin buscar los nombres de las claves o los nombres de las columnas relevantes. –

1

He visto muchas aplicaciones basadas en Ajax que realizan una comprobación en tiempo real de campos como el nombre de usuario (para ver si ya existe) tan pronto como el usuario abandona el cuadro de edición. Me parece un mejor enfoque que dejar en la base de datos una excepción basada en una restricción db: es más proactivo ya que tiene un proceso real: obtener el valor, verificar si es válido, mostrar error si no es así, Permitir continuar si no hay error. Entonces parece que la opción 2 es buena.

+0

Eso es bueno para el UX, pero una gran condición de carrera es que la ui aún debe lidiar con el envío de los datos. –

+0

Salimos de la condición de carrera haciendo una validación completa del servidor en el envío. La validación por campo es solo una conveniencia para el usuario –

+0

@Mark: seguro.Me pregunto cómo las personas manejan la "validación completa del lado del servidor" en la interfaz de usuario (asumiendo ajax para enviar los datos) cuando ocurre una violación *, que usted respondió en su otra respuesta, gracias. –

3

El problema es realmente una de las limitaciones de la arquitectura de su sistema. Al presionar toda la lógica en la base de datos, debe manejarla en dos lugares (en lugar de crear una capa de lógica de negocios que vincule la UI con la base de datos. Por otra parte, en el momento en que tiene una capa de lógica de negocios, pierde todo el beneficios de tener la lógica de procedimientos almacenados. no abogando por una o la otra. el tanto chupar casi por igual. o no chupar. Dependiendo de cómo se mire.

dónde iba? derecho.

Creo que una combinación de 2 y 3 es probablemente el camino a seguir.

Al evitar el error, puede crear un conjunto de procedimientos que se pueden invocar desde el código de interfaz de usuario para proporcionar un instrumento detallado retroalimentación específica para el usuario. No necesariamente tiene que hacer esto con ajax campo por campo, pero podría hacerlo.

Las restricciones únicas y otras reglas que se encuentran en la base de datos se convierten en la última comprobación de cordura para todos los datos, y pueden suponer que los datos son buenos antes de ser enviados, y arrojar Excepciones por rutina (la premisa es que estos procedimientos siempre deben invocarse con datos válidos y, por lo tanto, los datos no válidos son circunstancias excepcionales).

+0

Eso es en realidad una combinación de 2 y 4, con el ajuste a 4 que en lugar de generar errores en el servidor de la aplicación se generan en el proceso almacenado. Por lo tanto, el proceso almacenado arroja excepciones para TODAS las infracciones de reglas comerciales (algunas se atraparon a sí mismas, otras las atrapó el DB). Me gusta. –

+0

Tienes razón ... –

2

En defensa del n. ° 4, SQL Server tiene una jerarquía bastante ordenada de niveles de gravedad de errores predefinidos. Como usted señala que es bueno manejar los errores donde está la lógica, me inclinaría a manejar esto por convención entre el SP y la abstracción de UI, en lugar de agregar un montón de acoplamiento extra. Especialmente porque puedes generar errores con un valor y una cadena.

5

No realizamos nuestra lógica comercial en la base de datos, pero tenemos toda nuestra validación en el servidor, con operaciones DB CRUD de bajo nivel separadas de lógica empresarial de nivel superior y código de controlador.

Lo que intentamos hacer internamente es pasar un objeto de validación con funciones como Validation.addError(message,[fieldname]). Las diversas capas de aplicación anexar sus resultados de la validación de este objeto y luego nos llaman Validation.toJson() para producir un resultado que tiene este aspecto:

{ 
    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" 
    } 
} 

este lado del cliente puede ser fácilmente procesada para mostrar los mensajes relacionados con los campos individuales, así como en general mensajes.

En cuanto a las infracciones de restricciones, usamos el n. ° 2, es decir, verificamos posibles infracciones antes de insertar/actualizar y anexar el error al objeto de validación.

+0

Noté que esto es lo que SO hace para escribir operaciones como votar. La respuesta tiene los campos "Correcto" y "Mensaje". Pero si envía comentarios demasiado rápido, lanzan una excepción de servidor 500 y la interfaz de usuario lo detecta (es decir, lo que hace mi ejemplo de código, que no me gusta). –

+0

Sin embargo, me gusta este enfoque, y no sería imposible hacerlo incluso con las verificaciones de reglas comerciales que se manejan en el db. El servidor de la aplicación solo tiene que traducir todas las respuestas a un estándar que la interfaz de usuario puede manejar. Lo cual no es algo malo para hacer, independientemente. –

1

Ésta es la forma en que hago las cosas, a pesar de que puede no ser el mejor para usted:

por lo general ir para el modelo de suscripción preferente, aunque depende mucho de su arquitectura de aplicaciones.

Para mí (en mi entorno) tiene sentido comprobar la mayoría de los errores en el nivel intermedio (objetos comerciales). Aquí es donde tiene lugar toda la otra lógica específica del negocio, así que trato de mantener aquí también el resto de mi lógica. Pienso en la base de datos como un lugar para persistir en mis objetos.

Cuando se trata de validación, los errores más fáciles pueden quedar atrapados en javascript (formateo, longitudes de campo, etc.), aunque, por supuesto, nunca se supone que esas comprobaciones de errores tuvieron lugar. Esos errores también se verifican en el mundo más seguro y controlado del código del lado del servidor.

Las reglas de negocio (como "solo se pueden tener tantos foos por día") se comprueban en el código del lado del servidor, en la capa de objeto de negocio.

Solo las reglas de datos se comprueban en la base de datos (integridad referencial, limitaciones de campo únicas, etc.). También evitamos verificar todos estos en el nivel medio para evitar golpear innecesariamente la base de datos.

Por lo tanto, mi base de datos solo se protege contra las reglas simples, centradas en datos, que está bien equipada para manejar; las reglas más variables y orientadas a los negocios viven en la tierra de los objetos, en lugar de la tierra de los registros.

+0

Agradable. Sin embargo, curioso: ¿qué haces con respecto a la condición de carrera en la que pasa un cheque en el nivel medio, pero falla en el nivel de datos (por decir, otro hilo que le pega al golpe)? –

+0

Sí, eso es totalmente la debilidad en este enfoque. En mi entorno particular, este es un caso tan raro que no tenemos que preocuparnos por ello. Sería una excepción bastante fea, cierto, pero la base de datos se mantendría intacta. – teedyay

Cuestiones relacionadas