Validación en Entity Framework
Metodologías e ideologías
Debo comenzar confesando que la moda domain-driven me parece ridícula. Es cierto que la programación orientada a objetos es estupenda… y que el modelo relacional también lo es. También es cierto que, al mezclarlos, se produce un desajuste que en inglés llaman impedance mismatch. Pero los intentos de "resolver" el problema mediante la metodología domain-driven consisten, básicamente, en cargarse todas las bondades del modelo relacional, de la arquitectura cliente-servidor y, de paso, la mayoría de los avances aparecidos y probados en estos cuarenta años de bases de datos relacionales.
No es éste el lugar para explicar todo lo que tengo en contra del DD, por lo que me limitaré a un solo argumento. ¿Existe alguna diferencia básica, digamos, entre una aplicación "de negocios" y una aplicación, digamos, como Word o un sistema operativo? Claro que la hay: el dinamismo de las llamadas reglas de negocio. Usted puede escribir una clase para un procesador de texto y atiborrarla de reglas sobre el tratamiento de las mayúsculas. Probablemente, dentro de cinco años esas reglas seguirán siendo igual de malas o apropiadas. En cambio, las reglas para el funcionamiento de una aplicación de negocios cambian constantemente. Si usted enchufa esas reglas como parte de métodos de las clases "de negocio", tendrá que contratar a un nerd granujiento a jornada completa, para que cada vez que el dueño del negocio se cambie los gayumbos, cambie la programación de las clases afectadas, recompile y reinstale el sistema.
Por supuesto, esto es lo que todo nerd desea: un trabajo fijo a jornada completa, ya sea cambiando detallitos insignificantes de la clase Order o "tuneando" el núcleo de su instalación personal de Linux, que le deje tiempo para conectarse al Facebook y para jugar a dragones y mazmorras. Es comprensible. Otra cosa es que sea racional o eficiente. O bueno para alguien excepto para el propio nerd.
La cabra y el monte
Las modas, al final, visten a personas reales. Quiero decir, que si al artista se le olvidó la ranura en el pantalón para hacer pis, el portador de la prenda ya se las arreglará para orinar; eso, o reventará. Por mucho domain-driven que haya infectado la cabecita del pobre programador, en cuanto necesite hierba, la cabra irá al monte… o reventará, ya sabe. ¿Un ejemplo? Teóricamente, en el mundo ideal de nuestros guitar heroes con acné, cada clase debería ocuparse de su propia validación. Pero, ¡oh, qué penita!, los programadores reales que trabajan con Entity Framework han adquirido la costumbre de acumular validaciones dentro del código del método SaveChanges (hay varias formas de conseguirlo).
¿Es eso bueno, o malo? Hay de todo. Esperar a SaveChanges para comprobar que una fecha de nacimiento no pertenece al siglo XII, por ejemplo, es una soberana estupidez. Pero si la regla depende del estado de una entidad, no le queda más remedio que enredar con el administrador del estado: la pureza domain-driven exige que este estado sea externo a la entidad. Por ese motivo, era tan complicado utilizar entidades con servicios WCF en la versión anterior de .NET. Y por ese motivo, gracias al cielo, Microsoft ha terminado ciscándose en la pureza y regalándonos self-tracking entities en la nueva versión. Viva lo impuro cuando resuelve problemas.
¿Y qué dice la sacrosanta teoría sobre todo esto? En honor a la verdad, la Programación Orientada a Objetos dice que, en este caso particular, la centralización es mala. Y tiene razón… si se tiene en cuenta el contexto sobre el que se desarrolla esta metodología: un mundo en el que las principales extensiones a una aplicación se producen principalmente mediante la incorporación de nuevas entidades. Pero este no es el escenario característico de una aplicación de ventas, de préstamos o de Bolsa, donde es más probable que aparezcan nuevas reglas para los viejos objetos que nuevos objetos con nuevas reglas. El impacto de la modificación de las nuevas reglas es mayor si el código que las controla está disperso a lo largo y ancho de los ficheros individuales de clases de entidades.
Y hay más: observe que estas reglas de negocio suelen referirse al estado observable de las entidades de negocio. Traducido a código fuente: las reglas de negocio, por lo general, afectan a propiedades públicas de estas clases. Al fin y al cabo, estas reglas son establecidas por analistas funcionales… o por el propio jefe del cotarro; es decir, por gente que no tiene por qué enredarse con protected, private e internal.
Cabalgando la ola
La centralización, por lo tanto, no siempre es mala; digamos, entonces, que aceptamos que una parte de las reglas de negocio se implementan como parte del mecanismo de sincronización de los cambios con la base de datos. Sin embargo, ¡seguimos necesitando al nerd! Para cada cambio en las reglas, sigue siendo necesario recompilar la aplicación… y redistribuirla.
No sólo eso: imagine que su aplicación la utilizan varias empresas. O incluso, que la utiliza una sola empresa, pero que ésta tiene sucursales en distintos países, con diferentes legislaciones e idiosincrasias. Cada vez que en China cambien las reglas de descuentos para sujetadores, en la sucursal de Groenlandia tendrán que reinstalar el maldito software de ventas.
¿Y si movemos esas reglas fuera de la aplicación? Hablo de ubicar las reglas en un fichero de texto, de manera que la aplicación lo lea, digamos que al iniciarse. El contenido del fichero sería traducido a código ejecutable (eso es muy sencillo, con toda la tecnología que ofrece .NET) que se ejecutaría como parte del método SaveChanges del contexto.
Será más sencillo si muestro un ejemplo de reglas. La sintaxis concreta que he utilizado imita a la de C#, pero está claro que las variaciones son posibles:
class Order
{
on insert
Created = DateTime.Now;
on update
Modified = DateTime.Now;
rule MustPaidBeforeShipping is
Paid || !ShipDate.HasValue;
}
La explicación:
- Las reglas se definen por clase. Esto es así por la forma en la que puede tener lugar la validación durante OnSavingChanges: el componente que ejecuta el script recorrerá los ObjectStateEntry modificados en el contexto. Para cada uno de ellos, se elegirá el conjunto de reglas a ejecutar o verificar de acuerdo al tipo de entidad asociado, y luego, al estado de la entidad.
- Es recomendable que se respete la herencia. Si una regla se define en una clase base, debe respetarse también en las posibles clases derivadas, siempre que una de estas no la anule.
- Como mínimo, se deben soportar dos tipos de reglas: verificaciones y asignaciones a propiedades. En el ejemplo, hay dos asignaciones a propiedades, separadas según el estado de la entidad, y una sola verificación, que al no mencionar el estado, se activará tanto para inserciones como para actualizaciones.
- Cada regla tiene un identificador asociado. El escenario típico de estas validaciones será un proceso WCF remoto. Imagine que asociamos un mensaje de error directamente con la regla: ¿en qué idioma debería estar? Un servicio de capa intermedia debe evitar atarse a un idioma concreto, porque puede estar siendo utilizado por clientes configurados para idiomas diferentes. De todos modos, debería ser posible sustituir el identificador por un literal de cadena, para casos sencillos en que no se prevé la traducción a otros idiomas.
- Las expresiones trabajarían, principalmente, sobre entidades aisladas, por lo que no habría que preocuparse por extensiones del tipo LINQ: si usted quiere verificar que el total de una orden no sea superior a un valor, por ejemplo, prográmelo donde realmente corresponde, es decir, en la base de datos. Entity Framework no le puede ofrecer garantías, llegados a este punto, de que el grafo de entidades asociados a la entidad modificada, esté completamente cargado en memoria. Sólo piense, por ejemplo, lo difícil que es verificar la unicidad de una columna en un proceso cliente, sin tener todas las entidades en memoria y sin lanzar una consulta adicional al servidor.
Sobre las expresiones soportadas: en principio, no haría falta complicarlas con índices y corchetes, por las razones antes expuestas. Las expresiones básicas serían constantes y "rutas de objetos" (object paths). Estas rutas siempre comenzarían por una propiedad de la clase que define la regla, o por una clase o espacio de nombres. En nuestro ejemplo, ShipDate.HasValue comienza por una propiedad de la clase Order, pero DateTime.Now es, evidentemente, una propiedad estática de una conocida clase. Este tipo de sintaxis es muy fácil de implementar y traducir.
Las expresiones, que son el núcleo de nuestro sistema, se traducirían como árboles de expresiones LINQ, que se pueden compilar a código nativo dinámicamente. En esto ya tengo también bastante experiencia: el sistema es sencillo y muy eficiente (lo he utilizado en filtros a la medida para un servidor RTD para Excel, implementado dentro de una aplicación de servicios de Windows).
En resumen
Se trata solamente, en esta etapa, de una propuesta: crear un componente que, a partir de un script externo, se ocupe de determinadas validaciones e inicializaciones dentro del Entity Framework. Es fácil señalar el método SaveChanges como el punto donde se insertaría el componente de validación. Probablemente existan más puntos donde se pueda actuar.
En principio, el script con las reglas puede estar ubicado en un fichero de texto. Esto sería apropiado si las aplicaciones clientes accediesen a las entidades a través de un servicio WCF. Sería este servicio quien centralizaría el manejo del script y determinaría las reglas a cumplir. Pero sería sencillo extender el mecanismo, en ausencia de una capa intermedia, ubicando las reglas en la base de datos, para simplificar el cambio de las mismas.
Por supuesto, escribo esto, en este momento, para recabar sugerencias.
Etiquetas: bases de datos, EF, ideas
21 Comments:
El desajuste entre la programación oriendada a objetos y el Modelo Relacional se resuelve simplemente integrando las "tablas" en los lenguajes de programación de aplicaciones. LinQ intenta algo parecido pero es un completo desastre.
Yo he estado usando una temporada el Entity Framework y he dado marcha atrás. Lo que ganas con las comprobaciones de tipos en tiempo de ejecución lo pierdes multiplicado por mucho por todas las pifias que tiene.
Respecto a lo del script. No hay que confundir tablas con clases.
Lo que está claro es que todas las reglas de negocio tienen que estar aseguradas y centralizadas por el SGBD, eso te lo enseñan en la primera clase de bases de datos.
Si después quieres repetir alguna validación en las aplicaciones para que sea más ágil, pues lo ideal sería leer el script de creación de la base de datos, porque allí ya lo tienes todo. Esto es difícil de conseguir, pero creo que habría que seguir esa línea. Las reglas de negocio son siempre reglas de base de datos. Un lenguaje del estilo de Tutorial D estaría muy bien para eso.
Las reglas no habría que definirlas por tabla y menos por clase, sino que deberían de ser simplemente aserciones que podrían involucrar cualquier número de tablas.
En el ejemplo que pones, Order debería de ser una tabla y no una clase y las fechas de modificación y creación debería de marcarlas el SGBD y no la aplicación. La otra regla se puede repetir en la aplicación.
En Tutorial D esa regla sería:
constraint MustPaidBeforeShipping IsEmpty(OrderShipDates not matching Orders where Paid)
Yo les propuse un nuevo operador con el que se podría hacer
constraint MustPaidBeforeShipping OrderShipDates matches Orders where Paid
Y les gustó el operador, pero no el nombre.
Utilizando la lógica de predicados puedes llegar mucho más lejos que con una sintaxis como la que propones y ya está todo muy estudiado.
Respecto a lo del script. No hay que confundir tablas con clases.
Es que ahí se trabajaría en el "nivel conceptual", es decir, actuando sobre entidades. Para las tablas, ya está el propio servidor de bases de datos.
Utilizando la lógica de predicados
Por supuestísimo, pero es ahí donde está la gracia: la lógica de predicados sería estupenda en la base de datos, no en el lado cliente. Es cierto que una parte podría moverse a una capa intermedia, pero (por los temas de conectividad del grafo de objetos), dudo de que sea fácil de implementar... o incluso que sea deseable: a nadie le interesa "cachear" permanentemente todo un grafo de objetos en una capa intermedia.
En dos palabras: no es una propuesta de arquitectura. Es un parche muy parcial para mejorar el uso de EF.
Yo he estado usando una temporada el Entity Framework y he dado marcha atrás.
Hombre... ha mejorado bastante. Ya "casi" te permite hacer lo mismo que hacías con los datasets :) Falta, por ejemplo, la posibilidad de cambiar el BatchUpdateSize, el uso de procedimientos que devuelven más de un conjunto de resultados, el uso de UDF's que devuelven tablas en SQL Server...
Y sobre todo, hay un problema, que para mí es crucial, y que no acaban de resolver: no hay una forma directa de hacer un mapping de una columna de tipo char(1) que almacene 'S' o 'N' a una propiedad de tipo Boolean. Sin eso, el principal sentido que le veía a todo el asunto, que era la compatibilidad entre diferentes SGDB, es una farsa.
La pena es que Microsoft haya tomado este desvío y que le haya costado cuatro años.
Alfredo, por cierto, ¿has visto un invento que se llama StreamBase, diseñado al parecer por el mismísimo Stonebraker? No tengo una opinión formada sobre su calidad. En principio, claro, es un bicho de especie diferente a las BBDDs normales. Pero la empresa que lo comercializa, por lo visto, le gusta jugar al secretismo. Sospecho que quieren ser juez y parte: el sector bancario, que sería uno de los principales clientes, es una tajada muy gorda.
Es que ahí se trabajaría en el "nivel conceptual", es decir, actuando sobre entidades. Para las tablas, ya está el propio servidor de bases de datos.
Entonces diría que no hay que confundir las "clases conceptuales" con las clases normales.
De todas formas no estoy de acuerdo. El nivel conceptual es un nivel informal, es decir que es impreciso e incompleto. El lugar para las reglas es el nivel lógico, y lo que hay que asegurar es que las tablas no contengan datos incoherentes como un pedido enviado pero no pagado.
la lógica de predicados sería estupenda en la base de datos, no en el lado cliente. Es cierto que una parte podría moverse a una capa intermedia, pero (por los temas de conectividad del grafo de objetos), dudo de que sea fácil de implementar... o incluso que sea deseable: a nadie le interesa "cachear" permanentemente todo un grafo de objetos en una capa intermedia.
En el lado del cliente es igual de estupendísima, y las bases de datos en red son igual de nefastas en el SGBD como en las aplicaciones. Lo que hay que hacer es tratar los datos usando el Modelo Relacional en los dos lados. Así es como desaparece el desajuste de impedancia ese.
Es decir que nada de grafos para tratar datos. Eso se descartó en los 70.
no hay una forma directa de hacer un mapping de una columna de tipo char(1) que almacene 'S' o 'N' a una propiedad de tipo Boolean.
Para mi eso es un detallito comparado con lo gordo. Por ejemplo eso se podría resolver fácilmente con una vista, pero el EF se lleva fatal con las vistas. Eso si que es una razón más que de sobra para tirarlo por la ventana.
Y lo de las actualizaciones es demencial. En vez de ejecutar un update o un delete te tienes que traer listas del servidor y mandarlas de vuelta. Cuando eliminamos en EF el tamaño del código se redujo un montón y se ganó mucha velocidad y el código se hizo mucho más flexible.
De todas formas yo no invertiría mucho tiempo en el EF. Cualquier día tiran con él como con casi todas las cosas de este tipo que hacen.
No he visto el StreamBase, pero lo voy a mirar ahora.
Parece que StreamBase es una especie de analizador de flujos de datos basado en SQL. Parece que analiza mensajes y que toma decisiones a partir de reglas.
Intercepta mensajes, extrae datos, los inserta en una base datos relacional en memoria y ejecuta triggers.
No es nada que no se pudiese hacer antes, pero supongo que al ser algo diseñado específicamente para eso, será rápido.
Me parece interesante, pero no creo que esté muy relacionado con lo que estamos hablando.
Lo que hay que hacer es tratar los datos usando el Modelo Relacional en los dos lados
Hummm, ¿y cómo consigues verificar la unicidad de claves sin arrearle una transacción iniciada en el cliente a la BBDD? Porque, incluso si transfieres la verificación al servidor, no hay garantía de que siga cumpliéndose la condición cuando quieras aprovecharla.
Te menciono la unicidad por ser el ejemplo más sencillo. Cualquier predicado que utilice cuantificadores tiene que irse al servidor para ejecutarse eficientemente. Es a eso a lo que me refería. Dije "grafo" porque los ORM's al final se montan una copia en forma de grafo de una parte de la BBDD. Pero me refería a cuánta información sería necesario cargar en un cliente para reglas más "serias".
no creo que esté muy relacionado con lo que estamos hablando
No, no tiene relación. Te preguntaba porque probablemente me toque hacer una aplicación cliente con este rollazo.
Al parecer, la cosa tiene que ver con comunicaciones bidireccionales. La mayoría de las BBDD se comunican con el modelo cliente/servidor: toda iniciativa parte del cliente. En software para Bolsa, sin embargo, la mayor parte del trabajo está en recibir eventos... con lo que tienes que montar un sistema de sockets adicional, servidores adicionales, etc. Si no lo he pillado mal, parece que esto del StreamBase integra los dos conceptos... y claro, aprovecha para montar todo un tinglado alrededor del concepto de "evento".
He encontrado esto en Amazon: Event processing in action. Ya te diré algo si descubro algo más... si al final resulta verdaderamente interesante.
Ya te entiendo.
Hay muchas reglas que no se pueden verificar en el cliente, pero los cuantificadores siguen siendo útiles en la base de datos del cliente.
Pero eso es igual usando tablas o grafos.
Una base de datos es una base de datos aunque esté en la memoria del cliente.
Además con lo que propones tú, en realidad estás utilizando un cuantificador de forma implícita para todas las "entidades". Si lo haces explícito entonces tienes mucha más potencia y puedes validar cosas como los agregados de las facturas.
Respecto al StreamBase, pues habrá que ver cada proyecto en concreto para ver si quita más trabajo del que da. Pero me imagino que también tienes que montar servidores adicionales.
Hola Ian y Alfredo,
Que bueno ver que hay nuevos articulos en tu blog.
Ian no estoy seguro lo que pretendes pero generalmente las validaciones derivadas de las reglas de negocio que sin duda no deben faltar en el SGBD, andan muy ligadas a que tipo de interfaz de usuario se usara. Algo que si es cierto es que las entradas del usuario que deben ser validadas de alguna manera, estas no pueden esperar a ser enviadas al SGDB para que estes las ajuste a la integridad de datos.
Podrias crear que se yo algun tipo de mecanismo sea este una clase y/o una interface que obligue a las entidades a implementar el cosumo de las reglas de negocios que tienes en tu script cuya idea me gusta mucho.
Hay un asunto que se llama DataAnnotations no se si te ayude que puede ser usado para esto.
Siempre hay que tener en cuenta que tecnologia se va a usar como interfaz de usuario (WinForms, WPF/Silverlight o ASP.NET), ya que sino ando mal varian entre uno y otro las validaciones.
No se si se puede poner enlaces externos aqui pero en este framework hay un ejemplo que se llama BookLibrary el cual tiene validacion al estilo EF 4.0
http://waf.codeplex.com/
Suerte y espero no herir alguna alma sensible.
Yo creo que en estos casos lo mejor sería simplemente usar un SGBD en el cliente y que se encargue de asegurar la lógica de negocio. Por ejemplo algo como el embedded Firebird.
Y además puedes replicar las tablas que te interese para acelerar muchas consultas. Incluso puedes hacer que sea el servidor el que actualice al cliente.
Por cierto, el Blogger funciona de pena estos días.
Hola todos,
Ian lo que necesitas algo asi como el Validation Application Block de la EntLib y asi tendras las reglas de negocio separadas de tus entidades y completamente configurables. Supongo que querrás extender las capacidades del VAB para agregarle o ajustarle algunos detallitos.
Tengo entendido que se deben aplicar reglas de negocios en todos los niveles de la aplicacion ej. en el cliente para facilitar y controlar las entradas de los usuarios y sin duda en el servidor por noble razon de seguridad e integridad.
¿Como va tu libro?
Saludos desde Republica Dominicana.
No se si se puede poner enlaces externos aqui
¡Por supuesto que sí! Usa la etiqueta A, que a veces salen cortadas las URL's en los comentarios:
WAF en CodePlex
Suerte y espero no herir alguna alma sensible.
:) Que se jodan las almas sensibles.
¿Como va tu libro?
Voy a sacar primero el curso. Al fin y al cabo, la primera "ola" de gente que lo va a descargar ya lo va a tener gratuito, por actualización de versiones anteriores.
Y además puedes replicar las tablas que te interese para acelerar muchas consultas.
Es buena idea; tengo que probarla. Hay un mecanismo de ese tipo para SQL Server, creo, pero es ese tipo de cosas que no miras hasta que no lo necesitas.
Tienes el SQL Server Compact, pero lo malo es que no tiene ni vistas ni triggers ni procedimientos almacenados, con lo cual no es muy útil.
Yo siempre que puedo me limito a dejar que las reglas de negocio las compruebe el SGBD y no me complico más la vida. Muchas veces tampoco es tan importante que los mensajes de error tarden un milisegundo menos en mostrarse.
¡Hola Ian!
Es un gusto leerte nuevamente (y no es lisonja). :)
A pesar de que ya conocemos las limitantes que tienen algunas tecnologías y estándares informáticos, los creadores de software seguimos empeñados en llegar a la salida del laberinto no sin antes haber marcado cada uno de los adoquines de sus pasillos. Es como si los paneles solares se hubieran inventado hasta haberse terminado por completo el petróleo.
Pero reconozco que este comportamiento un tanto masoquista de intentar todo antes de cambiar de modelo es algo hasta cierto punto predecible en los informáticos: vemos en cada obstáculo del diseño un sabroso reto intelectual.
Supongo que algunos ya han dado el salto proponiendo nuevas y revolucionarias formas de hacer las cosas, como que una instancia de objeto pueda existir y operarse “cuánticamente” en cualquier capa, sin necesidad de puentes de código de mediano o alto nivel, y empleando un mismo lenguaje orientado a objetos para todo (base de datos, reglas de negocio, interfaces de usuario, etc.).
Pero claro, seguramente el costo de ingeniería (hardware y software) de algo así es algo que pocos o ningún emporio de los actuales estaría dispuesto a cubrir. Después de todo el negocio está en seguir recorriendo un laberinto de dos dimensiones nacido en el siglo pasado, no en construir uno de tres dimensiones más interesante, complejo, pero sobre todo útil y propicio para este siglo.
Dejando un momento el paréntesis futurista, y secundando en parte tu idea de los archivos de texto para reglas de negocio, creo que sería mejor que la propia base de datos tenga tablas para contener esa información, como de hecho es normal que las tenga (metadatos) pero de forma más extendida. Imagino que ERPs como los de SAP eso tienen para configurar en la propia base de datos todo tipo de reglas de negocio.
Saludos.
Al González.
La revelación de Sturgeon dice que "el 90% de cualquier cosa es mierda".
Y en el caso de la informática se queda bastante corta.
Estoy de acuerdo en que los metadatos son datos, y por tanto donde mejor están es en la base de datos, pero dudo muchísimo que los ERP como SAP tenga mucho de esto. Los ERP como SAP suelen manejar los datos de una forma muy primitiva sin aprovechar mucha de la potencia de los SGBD. Muchos provienen de una época más primitiva y están lastrados por eso entre otras cosas.
Bueno aunque nunca he participado activamente en el blog soy viejo seguidor.
Lo que quiero aportar es en relacion a los ERP. No se SAP pero NAVISION (Microsoft), que es el que conozco y he programado si que contiene en la base de datos no solo las reglas de negocio sino tambien los objetos que componen el la capa de presentación.
Por cierto el leguage que usa es muy parecido a Delphi.
Un abrazo a todos y en especial a Ian, espero que haya mas actividad en el blog a mi me enriquecen mucho vuestros comentarios yconocimientos. Gracias.
Hola, Al.
vemos en cada obstáculo del diseño un sabroso reto intelectual
:) Efectivamente, así es más divertido.
@Pedro & Al:
NAVISION (Microsoft), que es el que conozco
Sí, es mejor idea meter esas cosas en la base de datos.
"el 90% de cualquier cosa es mierda"
Profunda verdad. La otra es que esto es una ingeniería, aunque se base en los resultados de una ciencia exacta. Por lo tanto, está muy sujeta a las modas.
Lo que yo estoy empezando a hacer es hacer las validaciones en el cliente mediante un script en iron python. Ojo, solo uso para validaciones y no para reglas de negocio, que aunque puedan confundirse no son exactamente lo mismo.
A veces abusamos de querer hacer un interfaz demasiado rígido, quizá no sea tan necesario comprobarlo todo, sería mejor darle la opción al usuario de forzar algunas comprobaciones sólo cuando quiera, porque a menudo el propio USUARIO SABE LO QUE HAY, LO QUE NO, LO QUE VALE Y LO QUE NO, si pretendemos que el programa sea un TERMINATOR quizá sea muy lento o muy caro.
Publicar un comentario
<< Home