sql-server web-services error-handling

sql server - ¿Cómo manejar las violaciones de restricción de db en la interfaz de usuario?



sql-server web-services (6)

Así es como hago las cosas, aunque puede que no sea lo mejor para ti:

Generalmente prefiero el modelo preventivo, aunque depende mucho de la arquitectura de la aplicación.

Para mí (en mi entorno) tiene sentido verificar 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.

En lo que respecta a la 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 verifican en el código del lado del servidor, en la capa de objeto de negocio.

Solo las reglas de datos se verifican en la base de datos (integridad referencial, restricciones 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.

Implementamos la mayoría de nuestras reglas comerciales en la base de datos, utilizando procs almacenados.

Nunca puedo decidir cómo pasar los errores de violación de restricciones 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 el código personalizado lo maneja con gracia, como return empty dataset que el código de la aplicación busca y vuelve a la capa ui. .

Y ahí radica el problema. Nuestro código ui se ve así (este es el código de servicios web de AJAX.NET, pero cualquier marco de AJAX funcionará):

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 doy cuenta de que esto es lo que stackoverflow hace cuando envía comentarios demasiado rápido: el servidor genera una respuesta HTTP 500 y la interfaz de usuario lo capta).

Como ven, 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 violaciones de reglas comerciales), ya que .NET propaga excepciones hasta el manejador onError() .

Esto se siente mal. Mis opciones, creo, son:

  1. captar la excepción de "violación de clave duplicada" en el nivel del servidor de aplicaciones y convertirla en lo que sea que la interfaz de usuario espera como bandera de "regla de negocios violada",
  2. adelantarse al error (por ejemplo, con un "select name from Foo where name = @Name" ) y devolver lo que el servidor de la aplicación espera como la bandera "regla de negocios violada",
  3. en el mismo estadio de juego que 2): aproveche la restricción única incorporada en la capa db e insert into Foo ciegamente insert into Foo , atrapando cualquier excepción y conviértala a lo que el servidor de la aplicación espera como la bandera "regla comercial violada"
  4. insert into Foo ciegamente insert into Foo (como 3) y deja que esa Excepción se propague a la interfaz de usuario, además haz que el servidor de aplicaciones eleve las infracciones de reglas de negocios como Exceptions reales (en lugar de 1). De esta forma TODOS los errores se manejan en el onError() (o similar) de la capa ui.

Lo que me gusta de 2) y 3) es que las infracciones de las reglas comerciales se "lanzan" donde se implementan : en el proceso almacenado. Lo que no me gusta de 1) y 3) es que creo que involucran verificaciones estúpidas 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 que use Exception s solo en circunstancias excepcionales .

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


El problema es realmente uno de una limitación en 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 lógica en los procesos almacenados. No abogar por uno u otro. Los dos apestan por igual. O no apestan. Depende de cómo lo mires.

¿Donde estaba? Derecha.

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

Al anular el error, puede crear un conjunto de procedimientos que pueden invocarse desde el código de interfaz de usuario para proporcionar al usuario comentarios detallados específicos de la implementación. No necesariamente tiene que hacer esto con ajax campo por campo, pero podría hacerlo.

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


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.


He visto muchas aplicaciones basadas en Ajax haciendo una verificación en tiempo real en 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.


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 un msg_id , gravedad y estado, y con un conjunto de argumentos de error. Cuando se utiliza de esta manera, un mensaje con el msg_id dado debe haberse ingresado en la base de datos utilizando el procedimiento almacenado del sistema sp_addmessage. 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.

Los argumentos de error se sustituyen en el mensaje de error resultante de forma similar a cómo funciona la instrucción printf en C. Sin embargo, si solo quiere pasar los argumentos a la interfaz de usuario para que la UI pueda decidir cómo usarlos, simplemente realice los mensajes de error no tienen 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 usar 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.


No realizamos nuestra lógica comercial en la base de datos, pero sí tenemos toda nuestra validación en el servidor, con operaciones DB CRUD de bajo nivel separadas de la lógica empresarial de mayor nivel y el código del 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 añaden sus resultados de validación en este objeto y luego llamamos Validation.toJson() para producir un resultado que se ve así:

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

Esto se puede procesar fácilmente desde el lado del cliente para mostrar mensajes relacionados con campos individuales, así como también mensajes generales.

Con respecto 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.