Grimpi IT Blog

junio 11, 2011

La subjetividad en el desarrollo del software

Filed under: Arquitectura, Opinion — grimpi @ 11:08 pm

Muchas veces tenemos que decidir a la hora de desarrollar la arquitectura de una aplicación, que tecnología, que frameworks, que patrones vamos a utilizar. Muchas veces se generan discusiones en el grupo, porque uno quiere usar algún ORM y otro prefiere ADO.NET directo, o se generan peleas por si usar mocking o no, por alguna metodología sobre otra metodología, etc.
Todas estas discusiones se argumentan del lado técnico y racional. Uno intenta demostrar que el uso de un ORM es mejor por tal y tal motivo. Que este framework es más productivo que tal otro. Que este patrón es más prolijo o no y un largo etcétera.
Y es verdad que muchas veces, no cabe dudas, para escenarios específicos, una tecnología es mejor que la otra. Sin embargo, hay muchas situaciones, donde la elección de una determina forma de hacer software no se debe a un análisis racional de las opciones existentes, sino simplemente a un gusto. Gusto que se debe porque uno tiene más experiencia en esa tecnología, o todo lo contrario, porque no la conoce y le gusta jugar con cosas nuevas, o simplemente gusto porque sí.
Y ahí es donde voy. El elegir una arquitectura de desarrollo porque sí. Jamás, never de los never, un arquitecto, un programador, un líder técnico puede argumentar que es preferible usar una tecnología sobre porque simplemente le gusta más. La subjetividad en el mundo de desarrollo está terminantemente prohibida. Se supone que es una ciencia exacta y que por tal motivo, no podemos dar lugar a la irracionalidad. El sentido común epistemológico de un programador en general es bastante ingenuo en ese aspecto. Sin embargo, la subjetividad está presente en las ciencias exactas. Fue Thomas Kuhn en su tesis sobre los paradigmas y las revoluciones científicas, el primero en sostener que los elementos subjetivos tienen un papel fundamental en la elección entre teorías científicas rivales.
Muchas teorías científicas se imponen simplemente por consenso entre un panel de científicos influenciado por el contexto político e ideológico del momento.
Ejemplo: El feto es un ser humano? Y la realidad es que un científico ateo va a dar una respuesta y un científico católico, va a dar otra respuesta completamente distinta. Ambos argumentaran desde lo racional y técnico. Pero en el fondo, esto no es más que una manera de “racionalizar” una posición política.
Un intento de objetivar la subjetividad.

Y en el mundo de software pasa lo mismo. Acaso un “militante” del software libre diría que tecnológicamente, .NET es superior a Java? Muy difícil.
Muchos podrán sincerar su pensamiento político y decir que prefieren Java porque es open source y punto. Pero otros se enroscaran en una larga
“flame war” para demostrar que tienen razón.
Ahora que pasa cuando la elección de una tecnología ni siquiera obedece a una posición política visible? A nadie se le ocurriría decir que el uso store procedures sobre queries representa una postura ideológica… Y la realidad es que así como muchos prefieren a las rubias sobre las morochas o viceversa, muchos prefieren store procedures sobre querys.
Por supuesto como dije antes, para una determinada especificación funcional o restricción tecnológica, puede haber una clara y objetiva racionalidad en elegir un producto sobre el otro. Sin embargo, hay un universo de situaciones, donde la ventaja de la elección de una tecnología sobre otra es más difusa y ahí entra el conflicto. Ahí es donde se abre una ventana para la del tabú del programador inteligente, para lo que dificilmente pueda reconocer, para la subjetividad mas irracional posible: “el porque si”.

Y como se soluciona esto?
Mal que le pese a un peronista, la mejor opción para estos casos es el diálogo y consenso.

marzo 1, 2011

Filosofía del Software: De Karl Popper a TDD y Selenium

Filed under: Arquitectura, Test Driven Development, Testing — grimpi @ 8:59 pm

Karl Popper junto con Kuhn, fueron probablemente los dos epistemólogos mas importantes del siglo pasado. El aporte más significativo que le debemos a Popper, es el falsacionismo como método de constrastación de una teoría científica.
Que dice el falsacionismo? Básicamente que no es posible probar la verdad de una teoría, pero si podemos refutarla. Entonces, un enunciado científico estaría vigente siempre y cuando, no haya podido ser refutado. Por ejemplo, si mi enunciado dice “No existe vida extraterrestre”, no existe forma de comprobar si este enunciado es verdadero, pero lo aceptamos mientras no se descubra vida exterior. Pero si se descubriera, entonces estaríamos en condiciones de decir la teoría que afirmaba que no existía vida extraterrestre era falsa.
Popper insistía mucho que las teorías científicas debían intentar ser falseadas lo máximo posible. Una buena teoría, es aquella que pudo superar exitosamente muchos intentos de falsearla.
Bien, y que tiene que ver esto con el software?

Todo. El software es excelente ejemplo de la corriente falsacionista popperiana. Jamás podremos decir que un software no tiene bugs. Pero no porque somos humanos y cometemos errores y la perfección es algo divino. No podemos decir que nuestro software no tiene bugs porque no hay forma de demostrarlo afirmativamente. Sin embargo, si podemos llegar a falsear esta afirmación si encontramos errores en el software que diseñamos.
Al igual que lo que afirmaba Popper, un buen software es aquel que ha pasado exitosamente muchos intentos por refutarlo (en nuestro caso, por hacerlo fallar).
El problema que testear un software, es una tarea compleja que debe ser llevada por distintas personas. Como ser un buen popperiano mientras testeamos?

  • Uso intensivo de pruebas unitarias: No importa si usamos TDD, BDD, TAD. No importa si usamos mocking o no. No importa si usamos xUnit, VSUnit, NUnit o no. Lo mas importante de todo, es que nuestro código tenga la mayor cantidad posible de pruebas unitarias (o de integración) posibles. Y esto no significa hacer un método de prueba por método de clase, sino tal vez, hacer varias pruebas unitarias por un solo método, si este es bastante complejo.
    Si nuestra clase esta expuesta para otros desarrollos, hay que probar también que pasa cuando se le ingresan valores no esperados (parámetros nulos, valores aleatorios totalmente fuera del rango esperado, etc.).
    Muchos programadores se sienten demasiado seguros cuando tienen muchos test unitarios en “green flag”. Sin embargo, este tipo de metodología lo que mejor garantiza es que una modificación de código no produzca que otro pedazo de código deje de funcionar. Pero no es suficiente.
    Las pruebas unitarias pueden no enfocarse solamente en probar el código base (sea C#, Java, Ruby o lo que fuese), sino también se podría hacer con Javascript y objetos de una base de datos.
  • Hacer stress testing: Muchas aplicaciones son desarrolladas en entornos incorrectos con un volumen de datos muy bajo en relación con el entorno que va a existir en producción. Esto es un gravísimo error. Porque después se sube la aplicación a producción, pensando que tiene una performance aceptable y nos encontramos al poco tiempo, con que las consultas a la base no estaban optimizadas, no hay estrategia de cacheo de datos, hay métodos que hacen uso intensivo del procesador que están mal programados y nos encontramos en producción con un montón de problemas de performance que no tuvimos en cuenta por desarrollar en un entorno muy distinto al real.
    Esto suele suceder por varias razones. En primer lugar, cuando desarrollamos una aplicación de cero, no tenemos un conjunto de datos reales de producción, entonces nuestras tablas están semivacías con los datos mínimos para poder desarrollarla. Todo funciona rapidísimo, nuestros reportes vuelan. Ahora cuando la tabla pasa de tener 20 registros a 5 millones en producción, recibimos un llamado diciendo “Houston, tenemos un problema…”
    Si no tenemos datos reales para desarrollar, debemos simularlos. Esto se puede hacer manualmente o con muchas herramientas que existen en el mercado. Visual Studio 2008/2010 tiene esa posibilidad. También Red Gate tiene un producto que se llama SQL Data Generator muy bueno.
    Es lo que se llama un test de carga de datos (Load testing).
    Otra situación que nos presente inconveniente es la concurrencia. Y esto es algo mucho más difícil de simular en desarrollo. Tener una base de datos grande es fácil. Ahora como hacemos para tener 10.000 usuarios concurrentes para desarrollo? Imposible. La mejor manera de hacer esto entonces es usar herramientas de stress testing que simulen un cuadro así. En Java existe una excelente herramienta llamada JMeter. Visual Studio ofrece una herramienta parecida (Web Application Stress). Existe este excelente portal con un compilado de distintas aplicaciones open source y gratuitas de testeo muy recomendable.
    Es lo que se llama un stress testing de concurrencia (Stress testing).
  • Test de funcionalidad: Bueno, este es el punto fuerte de QA. En una organización grande no puede faltar. Se debe verificar que el sistema haga lo que esta contemplado que haga. Se debe tener un buen documento de casos de usos y que resultados se esperan. En una empresa donde el testeo es mas “casero”, una de los principios básicos es que el que desarrollo la aplicación, no debe testearla.
    Una muy buena herramienta para automatizar test funcionales de aplicaciones web es Selenium.
  • Test de UI: Esta relacionado con el test de funcionalidad, pero se dedica a buscar errores en la UI (que son mucho mas difíciles de ser detectados en una prueba unitaria). Las validaciones son correctas? Que pasa si se deja campos en blanco o se ingresan valores extremos? Se pide confirmación cuando se van a realizar una operación de borrado o critica del negocio? Como funciona en distintas configuraciones regionales y en distintas resoluciones de pantalla? Como se muestran los errores de aplicación? Como funciona en los distintos navegadores? Que pasa si deshabilito la ejecución de javascript?
    También se deben probar elementos relacionados con la seguridad. Que pasa si modifico manualmente los valores de un querystring? Esta cubierta contra ataques de SQL Injection y XSS Injection? Que pasa si una aplicación externa intenta inyectar peticiones GET y POST a nuestra aplicación?
    WaitR y WaitN (lamentablemente discontinuado) son muy buenas herramientas para automatizar este tipo de test. También podemos usar Selenium.

Links Interesantes:
Performance Testing Guidance for Web Applications
Open Source Testing

diciembre 20, 2010

Programando bajo Assumptions Driven Development (ADD)

Filed under: Arquitectura, Opinion — grimpi @ 4:24 pm

Antes de empezar este post, quiero advertir al lector, que el dueño de este blog no está muy de acuerdo con el modo en que se está desarrollando software actualmente. La mayoría de las posturas del autor, son contrarias a las del “mainstream” informático.

Una de las cosas que actualmente se utiliza mucho en el desarrollo de software, son los supuestos. Qué pasa si el día de mañana cambiamos de base de datos? Qué pasa si el día de mañana en vez de escribir en una base de datos, escribimos en un xml? Qué pasa si el día de mañana cambiamos de framework de logging? Qué pasa si el día de mañana nuestro sistema desarrollado para correr en un servidor Windows 2008 debe correr en un celular Android?
En definitiva, planteamos una serie de supuestos posibles cambios tecnológicos o de infraestructura que podría sufrir nuestra aplicación, pero nunca evaluamos la posibilidad de que tal cosa efectivamente suceda. Y estos supuestos no son gratis, porque requieren de capas de abstracciones que agregan más código, necesidad de uso de frameworks y nos limitan nuestras posibilidades.
Es lo que yo llamo Assumptions Driven Development (ADD). Desarrollo a base de supuestos.
Lamentablemente hoy en día, es casi la regla trabajar bajo esa “metodología”, pero cuya importancia esta terriblemente sobrevalorada. El gran mito de la portabilidad.

Si una empresa trabaja hace 10 años con un motor de base de datos especifico, por ejemplo, SQL Server y no hay ningún proyecto a corto y mediano plazo de migrar a Oracle, cual es el sentido de hacer una aplicación especifica para esa empresa que sea portable por si el día de mañana se cambia de motor, sabiendo que es muy poco probable que tal cosa suceda? Y como dije antes, los supuestos no son gratis. Nos limitan tanto en productividad como en performance. Si yo quiero hacer que mi sistema sea portable, entonces estoy desaprovechando muchas características que son propias de una determinada tecnología. Puedo usar store procedures en vez de tener un ORM que me autogenere consultas SQL genéricas, puedo usar características avanzadas de SQL Server incorporadas en SQL 2005 y 2008 que nhibernate o cualquier ORM no soportan. Lo mismo aplica a otros motores. PostgreSQL tiene algo fantástico que son los campos vectores. Es algo muy específico de PostgreSQL y lamentablemente si quiero portabilidad, difícilmente pueda usarlos.
Y no solamente con las bases de datos. La metodología ADD se usa en casi todas partes y junto con la utilización de mocking, es responsable del gran auge que está teniendo los frameworks IoC y la inyección de dependencias. Realmente necesito inyección de dependencias en mis clases para otra cosa que no sea mocking? Realmente necesito crear tantas interfaces para mis clases puedan ser reemplazadas por otras? Puede que sí, depende de las características del negocio y la aplicación, pero si vamos a usar DI solamente para un eventual cambio de librería de logging o de acceso a base de datos, me parece que hay que hacer un stop y pensar nuevamente si realmente vale la pena ensuciar el código con esto.
Porque ese es uno de los costos que pago para ser en muchos casos, innecesariamente flexible. Y la realidad que el uso de tantos frameworks a veces a uno le hace perder un poco el control de la aplicación.
Otro supuesto también muy común, es la escabilidad. Nuestro sistema “debe ser” escalable de antemano, aun si estamos desarrollando un software especifico para una pyme que van a usar 20 empleados. Esto significa hacer uso de estrategias de caching (con su respectivo framework) muchas veces innecesariamente.
La pregunta que hay que hacer es: tiene sentido pensar en portabilidad si la vida util del sistema es de 3 o 4 años maximo? Tiene sentido tanta flexibilidad y portabilidad? Estos supuestos, es común que ocurran? Y la verdad que no siempre sucede.
Y me atrevería a decir, que son minoría.

Por supuesto que si yo estoy desarrollando un software ERP genérico que quiero venderlo al mercado, la portabilidad es una gran ventaja, porque me permite venderlo a clientes que utilicen distintas plataformas. En ese caso, cuando se inicia el desarrollo del proyecto, no queda otra que pensar en portabilidad. Pero la mayoría del software que hacemos en general, no es genérico. Es específico para una empresa particular cuya infraestructura no suele ser migrada radicalmente o es una página web en donde los cambios de arquitectura e infraestructura tienen otra dinámica totalmente distinta y generalmente, cuando se produce un cambio de este estilo, es necesario reescribirla gran parte y no por una cuestión de no haber pensado en estos supuestos cambios, sino porque los casos de uso de nuestra aplicación cambiaron mucho. La vida util de un sistema web suele ser por otra parte mucho mas corta.

Ojo, yo no planteo tirar por la borda todo y no pensar en posibles cambios tecnológicos que puedan existir en la aplicación, pero si tener un poco más de cuidado, evaluar los costos que significan estos supuestos y ver si realmente existe la posibilidad de que tal cosa exista. Dejar de tener miedo en “casarse” con una tecnología y aprovechar al máximo, todas las bondades que ofrece.

diciembre 17, 2010

Como hacer unit testing contra la base de datos?

Filed under: .NET, Arquitectura, Mocks, Test Driven Development — grimpi @ 6:30 pm

Hoy en día el desarrollo de software, no se concibe si unit testing. Hasta el famoso “Hola Mundo” debe tener su respectiva clase de testing…
Uno de los principales problemas que surge a la hora de crear los test unitarios, es como probar contra la base de datos.
Donde está el problema? Supongamos que hacemos un test unitario “Login” que valide el usuario con una password incorrecta ingresada a propósito para ver falla el login. Y supongamos que en la lógica de negocio, al tercer intento fallido de login se bloquee el usuario.
Que va a pasar si tenemos otro test unitario, que intenta probar el login exitoso si ya testeamos tres veces el test unitario anterior? Va a fallar, porque en la base de datos, el usuario quedo marcado como bloqueado.

Para ser más claro, si nuestros test unitarios “ensucian” la base de datos, existe una gran posibilidad de que fallen y no por culpa de que están mal escritos o hay un bug en el código, sino porque los datos no son los que esperamos que fuesemos.

Para solucionar este problema, existen diversas técnicas:

  • Resetear los datos de la base de datos antes de cada tests.
  • Hacer que el test corra dentro de una transacción distribuida y luego hacer un rollback de la transacción.
  • Resetear solo los datos usados por el test, después de que este haya corrido.
  • Usar mecanismo de versionado de datos incorporados en las nuevas versiones de SQL Server o Oracle (flashback tables).
  • Separar el test unitario de la base de datos (mocks).

Obviamente cada una de estas técnicas, tienen sus respectivas ventajas y desventajas. Vamos a empezar de la simple, a la más compleja.

Resetear los datos de la base de datos antes de cada tests.
Esta es la primera opción que viene a la mente. La principal ventaja es su facilidad de implementación.
Una opción practica para hacer esto es tener un backup de la base de datos “limpio” y restorearlo cada vez que se ejecuta un unit testing.
Otra opción es tener un set de datos especifico necesario para el testeo y cargarlo manualmente. Luego del testo, estos datos son reseteados. Existen frameworks como NDbUnit (hhttp://code.google.com/p/ndbunit/) o TDUnit (http://tdunit.codeplex.com/) que nos facilitan enormemente esta tarea. Básicamente funcionan configurando un xml de datos y llamando a una instrucción de carga de datos en cada test unitario.

Ejemplo de código con NDBUnit:

public void Test()
{
string connectionString = “server=localhost;user=dbuser;password=dbpassword;initial catalog=MyDatabase;” ;
NDbUnit.Core.INDbUnitTest mySqlDatabase = new
NDbUnit.Core.SqlClient.SqlDbUnitTest(connectionString);

mySqlDatabase.ReadXmlSchema(@”..\..\MyDataset.xsd”);
mySqlDatabase.ReadXml(@”..\..\MyTestdata.xml”);
}

La principal desventaja de este sistema es obvia. Performance. Limpiar y cargar los datos por cada test unitario, puede ser algo extremadamente costoso en cuanto a tiempos.

Hacer que el test corra dentro de una transacción y luego hacer un rollback de la transacción

Este mecanismo es tentadoramente simple, sin embargo, pocas personas lo deben usar.
Cada test que se corre, está dentro de una transacción distribuida (COM+) que al finalizar, hacemos un rollback. De esta manera extremadamente simple, mantenemos el estado de los datos de la base de datos en cada test.
El problema de este enfoque, es que las transacciones distribuidas son las lentas que las locales. Por otro lado, en determinados entornos, hacer que las transacciones distribuidas funciones, requieren de cierta configuración de Windows.
Existe una extensión para NUnit llamada XtUnit, que entre otras cosas, permite ponerle el atributo [Rollback] al método de testing y automáticamente este unit testing va a correr dentro de una transacción distribuida sin necesidad de que tengamos que escribir código adicional.

Ejemplo de Test con XtUnit:

[Test,DataRollBack]

public void Insert()

{

CustomerService custom = new
CustomerService();

custom.add(“Esteban”,29,21452367);

}

Ejemplo de test sin NUnit solo:

[Test]
public void Insert()
{
using (TransactionScope scope = new TransactionScope())
{
CustomerService custom = new
CustomerService();

custom.add(“Esteban”,29,21452367);
scope.rollback();
}
}

Resetear solo los datos usados por el test, después de que este haya corrido

Este enfoque evita tener que resetear toda la base de datos antes de que el test sea ejecutado. En cambio, se carga una serie de comandos típicamente de insert/delete antes y después de cada test. Las buenas noticias es que de esta manera el menor el trabajo adicional que se ejecuta y por lo tanto, mejora significativamente la performance.
El problema de esta técnica, es que uno debe conocer con antelación que datos va a necesitar el test y si nos olvidamos de insertar un dato necesario, puede que falle el test unitario no por un bug o error de diseño, sino por un simple olvido a la hora de insertar un registro en una tabla antes de que sea ejecutado el test.

Usar mecanismo de versionado de datos incorporados en las nuevas versiones de SQL Server o Oracle

Este es un enfoque bastante interesante y que nos ahorra escribir código. Las últimas versiones de las bases de datos modernas, incorporan la capacidad de hacer versionado de datos sobre las tablas. Cada vez que se hace un insert/update/delete, el dato anterior es guardado en otra tabla interna del motor con un timestamp, existiendo la posibilidad más adelante de consultar el estado de los datos en un momento determinado y de volver la tabla a ese estado.
En Oracle esta característica se llaman Flashback table y en SQL Server Change Tracking.
La ventaja de esta técnica es que no tengo que escribir manualmente el reseteado de los datos ni tengo que lidiar con transacciones distribuidas. Sin embargo, las desventajas son múltiples. En primer lugar, esta técnica solo funciona con las versiones más modernas de las bases de datos Oracle, SQL Server y Postgres. Por ejemplo, no sería posible hacerlo con SQL Server 2005. MySQL en ninguna versión tiene esta característica. Por otra parte, habilitar esta opción en la base de datos tiene un altísimo impacto en performance y en tamaño en disco. Por último, para habilitar esta opción, hay que hacerlo desde el motor de la base de datos.
Para pesar de todas estas desventajas, creo que es una técnica útil en determinados casos puntuales. Especialmente en situaciones donde no tenemos tiempo o no podemos escribir código.

Separar el test unitario de la base de datos (mocking)

Aquí presentamos la opción supuestamente “correcta” de la industria actualmente. Evitar a toda costa el test unitario contra la base de datos y reemplazarlo por mocks, que son clases que emulan el comportamiento de una base de datos (o de cualquier otra cosa que queramos, como web services por ejemplo), de tal manera que podemos probar si el método funciona como deseamos sin conectarse con la BD.
Existen varios frameworks de mocking (NMocks, Rhino, Typemock y Moq). En general son bastante parecidos en cuanto a funcionalidades básicas, aunque algunos son un poco más claros que otros. Personalmente me siento más cómodo trabajando con Moq.

Usar mocking como método de testeo unitario en reemplazo de la base de datos tiene varias ventajas. La primera es la velocidad. No es lo mismo ejecutar 300 test unitarios contra la BD que contra clases en memoria.

La segunda ventaja es la inmutabilidad de los datos. Al no correr contra una base de datos, no se modifica realmente ningún dato, lo que permite que se pueda simular siempre el mismo estado. Para esto es necesario que se cargue al objeto mock con el que se prueba, los valores iniciales que se necesitan, para que el test unitario no falle. O sea, el uso de mocking nos evita todos los problemas que vimos en las 4 soluciones del anterior post.

El uso de mocking también agrega mas previsibilidad al test unitario, cuando no tenemos mucho control sobre la fuente contra la que se prueba o esta tiene un comportamiento indeterminado, como por ejemplo, un monitor de red que prueba x tipo de paquetes. Son situaciones muy específicas y puntuales, pero en esos casos el uso de mocking resulta casi indispensable.

Pero este enfoque también tiene sus desventajas. En primer lugar, no es una prueba “real”. Es una simulación. Hay situaciones en las que un test unitario contra un mock puede resultar exitoso, pero en un escenario más real, contra la base de datos, puede que falle. Por ejemplo, en el caso de que un método llame un store procedure y devuelva determinado conjunto de datos. Si el store procedure fue por cuestiones de negocio modificado sin nuestro conocimiento de tal manera, que el resultado de salida cambie, si probamos contra el mock nunca nos vamos a enterar de esta modificación.

También puede darse el caso de que haya cambios en la estructura de una tabla o falten permisos. Todas situaciones que si no tenemos aviso, podrían hacer que el test unitario corra exitosamente cuando no debería.

Otra desventaja de esta técnica, es que no es inerte a como está el código de la aplicación. Para ser más claro, para que una clase de nuestra aplicación, sea “mockeable” debe estar programada para esto.

Debe estar hecha de tal manera, que uno pueda inyectar instancias distintas. Uno puede decir y de manera bastante correcta, que es una buena práctica que las clases permitan inyección de código. Puede ser, pero también es discutible. Depende de las características de la aplicación.

Pero el caso es que uno no puede mockear una clase si no pensó antes en que iba a usar esta técnica de test unitario. Claro que si uno trabaja con TDD no tenga este tipo de problemas, pero no todo el mundo trabaja así y ni tiene porque hacerlo.

Otra desventaja es que uno tiene que decirle a la clase mock, que valores espera. Las expectations. Ejemplo:


Lo que resulta un poco engorroso a veces si la función es compleja.

Como verán, soy bastante crítico de este tipo de soluciones. Mi principal problema es que agregan complejidad al código y más complejidad, es mayor probabilidad de errores. El test unitario tiene que ser lo más simple posible, tener la menor cantidad de errores. Y agregar mocks significa todo lo contrario.

Por supuesto, que se gana en velocidad. El tema es el siguiente: Constantemente estamos corriendo 300 test unitarios? Yo personalmente solo corro los tests unitarios de los métodos y clases que estoy tocando en este momento o calculo que pueden llegarse a verse afectados. En todo caso, al final del día, se corre todos los test para verificar que no se suba nada que rompa el código, pero tiene sentido hacerlo continuamente?

Y si no es así, entonces cuanto es la ganancia de velocidad?

También uno puede plantear soluciones intermedias. Probar las clases repository o DAO o aquellas clases de bajo nivel cuya función es trabajar con la base de datos sin mockeo y las clases de negocio, usando mock. De esta manera, al probar tanto contra la base de datos y contra una clase mock, estoy ganando fiabilidad en el test (al probar los repository directamente contra la BD) y por otro lado, también gano velocidad al evitar testear las clases de negocio contra la BD.

Muchos dicen por otra parte, que no hay que confundir un test de integración con un test unitario. El test de integración se prueba con la base de datos y el test unitario no. Es una práctica habitual escribir estos 2 tipos distintos de test para una misma función. Es una opción bastante prolija, pero el costo es la duplicación de código de testing.

De todas maneras, más allá de los gustos personales de cada uno, esta solución existe y puede ser altamente recomendable en determinadas situaciones puntuales.

Como ya deberíamos saber, cuando uno desarrolla, existen múltiples formas de hacer lo mismo indistintamente en el 80% de los casos. Pero siempre, existe ese 20% restante, donde determinada forma es claramente más conveniente que las otras.

marzo 11, 2009

Como almacenar passwords en una base de datos?

Filed under: Arquitectura, Seguridad — Etiquetas: — grimpi @ 9:53 pm

Supongamos que tenemos que hacer el típico sistema donde el usuario se autentica contra una tabla de nuestra base de datos.
Entre todas las cuestiones de seguridad que hay que tener en cuenta, surge un problema muy común: De que manera almacenar el password de los usuarios en nuestra base de datos?

Existen 4 maneras clásicas de hacer esto:

  • Guardarlo así como viene, en un campo sin encriptar, como si fuera un campo de texto normal.
  • Guardarlo encriptado.
  • Aplicarle una función hash y guardar el hash del password.
  • Aplicarle una función hash + un salt y guardar el hash del password.

Password plano:

La primer opción, por obvias razones, es la más simple de todas, pero extremadamente insegura y salvo en sistemas donde la seguridad NO IMPORTE EN ABSOLUTO, no debe ser utilizada. Sin embargo no es infrecuente encontrar sistemas donde nadie se haya tomado la molestia al menos, de dificultar mínimamente el acceso al password.
Un argumento que se puede dar a favor de este enfoque, es que si el acceso a la base de datos y la tabla de usuarios, está correctamente configurado y restringido, no debería haber problemas de seguridad. Lo que es una mentira atroz, porque tanto el DBA como los desarrolladores, tendría potencialmente acceso a estos datos.
Y si un hacker termina rompiendo de alguna manera la seguridad de nuestra base de datos, podría llegar a leer sin ningún problema, el password de todos los usuarios.

Password encriptado reversible:

La segunda opción, consiste en encriptar el password. Es mejor que nada, pero tiene sus problemas. El primer inconveniente que surge con este enfoque, es donde guardar la llave de encriptación. En un archivo de configuración? De qué sirve usar el algoritmo de encriptación más complejo y seguro que exista, si luego guardamos la llave de encriptación, en un archivo plano o de fácil acceso?
Y el segundo problema de este enfoque, es que el password es reversible. Que significa esto? Que es posible obtener el password sin apelar a la fuerza bruta. Por lo tanto, una potencial debilidad.
Si se pueden desencriptar, el riesgo de que alguien conozca la llave y tenga acceso a todo el sistema con todos los usuarios es muy grande. En un organización para el programador/DBA resulta extremadamente fácil obtener el password de los usuarios del sistema que mantiene/desarrolla, ya que tiene acceso a las tablas y a la clave de encriptación.

Password encriptado irreversible:

Perfecto, ya vimos que las primeras 2 opciones, son bastante débiles. Qué hacemos?
La tercera manera de almacenar el password, es usar una función hash. Como es esto?
Una función hash, es una función que encripta un determinado texto de forma no reversible, esto significa que no existe forma de “desencriptar” dicho texto. Lo que es perfecto para almacenar password. En la tabla ahora tenemos que guardar el hash del password. Es un método mucho más seguro que los otros 2 anteriores. Al no poder ser “desencriptado” el password, no existe forma de obtenerlo.
Entonces, si no puedo desencriptar una contraseña, ¿Cómo puedo saber entonces si una contraseña entregada es válida? La respuesta es muy simple. Se encripta la contraseña entregada y se compara el resultado de la encriptación con la contraseña previamente almacenada.

CLAVE_HASH = FUNCION_HASH(Password)

Existen principalmente 2 algoritmos para hacer Hash: MD5 y SHA-1 (también existen variantes de este como el SHA-256 y SHA-512).
Sin embargo, existe una debilidad, veamos el siguiente ejemplo de una tabla Usuario:

ID Usuario Password
1 Pepe ADC2-1234….
2 Rosario ADC2-1234….
3 Juan FA3D-BC56….
4 Grimpi FFD3-D045….

Si somos observadores, el hash del password de los usuarios Pepe y Rosario son el mismo. No podemos saber cuál es el password exacto de ambos, pero si podemos saber que son iguales. Supongamos que nuestro sistema tiene 50000 usuarios. Cuál es la posibilidad de que existan passwords repetidos? Muy grande. Si un Hacker por alguna razón, obtiene el password de Pepe, instantáneamente, ya sabría cual es el password del usuario Rosario. Mala idea.
Por otro lado, este enfoque también tiene otro grave inconveniente, que son los diccionarios de claves hash (Rainbow tables). Un diccionario de estas características, no es más que una enorme tabla de 2 campos, donde en el primer campo figura la clave hash y en el segundo campo, el texto plano correspondiente a esa clave.
Los usuarios no suelen poner claves muy complejas a excepción que uno se lo exija, por lo tanto, un diccionario de claves hash muy amplio, seguramente contendrá la mayoría de las password de los usuarios.

Entonces, que hacemos?

Password encriptado irreversible + Salt:

Que es un salt?
Un salt no es ni mas ni menos que texto o un conjunto de caracteres que varían por usuario/registro que se le concadena al texto antes de ser encriptado por una función hash. Y para qué queremos hacer esto? Para evitar los problemas del enfoque anterior. Si a un password se le concadena un texto que varia por usuario, como por ejemplo, el ID del mismo o el login, el hash sería diferente aun si tuviésemos 2 o más usuarios con exactamente el mismo password.
Pero además, dificultamos enormemente el ataque del hacker usando un diccionario de claves. Por supuesto, que a medida que el Salt sea más complejo, más dificultosa será la tarea del hacker.

CLAVE_HASH = FUNCION_HASH(Password + Salt)

Existen básicamente 2 estrategias diferentes para determinar el salt de un password. Recordemos que a la hora de autenticar el password, debemos saber el valor del salt para generar una clave encriptada idéntica a la que tenemos en la base de datos.

La primera estrategia consiste en generar un texto aleatorio (convenientemente con caracteres “raros” para dificultar todavía más la tarea de un ataque por diccionario) y asociárselo al usuario, por lo tanto, sería necesario agregar un nuevo campo a la tabla de Usuarios, que tenga el valor del Salt:

ID Usuario Salt Password
1 Pepe AB_D!#$2 BFC3-8234….
2 Rosario T5&/021? AAC2-1290….
3 Juan ¿+}s34@” EC3D-4C56….
4 Grimpi D*-34r$Q AFC4-A145….

La otra estrategia consiste calcular el salt en función de algún dato del usuario, como por ejemplo el username (que se supone que debe ser único), el ID de la tabla o algún otro campo del mismo. La ventaja de esta estrategia es que no tenemos que agregar un nuevo campo a la tabla, pero debemos asegurarnos que usamos un valor univoco en toda la tabla!!.

Recomiendo ver este link con una interesante discusión que hubo en un foro, con respecto a las distintas maneras en que se puede obtener el valor salt y este paper que explica mejor el metodo recomendado.

febrero 6, 2009

Introducción a Test Driven Development (TDD)

Filed under: Arquitectura, Patrones, Test Driven Development — Etiquetas: — grimpi @ 10:46 pm

Test Driven Development (TDD) es una metodología de programación que involucra 2 prácticas: Escribir primero las pruebas unitarias, o sea, escribir el test antes de escribir el programa y refactoring.
Yo creo que una de las cosas más difíciles al trabajar de esta manera, es cambiar la mentalidad con la que estamos acostumbrados a desarrollar.
TDD es un proceso interactivo donde se repiten una serie de pasos hasta que se está satisfecho con el código generado. A TDD no le importa demasiado como está estructurado el código ni la arquitectura del software. La manera en que TDD trabaja es mediante requerimientos o casos de usos, que son descompuestos en un conjunto de comportamientos más chicos necesarios para cumplir con el caso de uso dado. Luego por cada comportamiento del sistema, lo primero que se debe hacer es escribir un test unitario para dicha funcionalidad.

Hay muchos conceptos confusos alrededor de esta técnica que me gustaría aclarar:

  • Primero las pruebas unitarias, luego el código de los métodos (Test-First Development). Este concepto es una de las características fundamentales de TDD. Si no respetamos esto, no estamos haciendo TDD (lo que tampoco tiene porque estar mal, pero es necesario hacer la aclaración sobre lo que no es TDD).
  • TDD no es una técnica de testing. TDD apunta a desarrollar software de manera incremental de la forma más simple y robusta posible.
  • Mucha gente tiene cierto rechazo porque piensa que TDD consiste en escribir TODAS las pruebas unitarias antes que el código. Esto es falso y es importante remarcarlo. TDD consiste en hacer un desarrollo cíclico e incremental. Se debe generar inicialmente un conjunto muy limitado de test que corresponden a una clase o caso de uso, para luego ir desarrollando el código. Una vez que toda la funcionalidad correspondiente a las pruebas unitarias fueron desarrolladas, se pasa a otro modulo.
  • A pesar de lo que siempre se dice que en TDD lo primero que hay que hacer es escribir el test unitario antes del código, esto tiene un límite obvio y es que la estructura de la entidad que vamos a probar ya debe estar creada. Por ejemplo, si vamos a desarrollar una clase que administre clientes, conviene primero crear la clase entidad Cliente, con sus respectivas propiedades, luego las pruebas unitarias y por último los métodos de la clase Cliente (que pueden ser insertar, modificar, validar, etc.).

Ciclo del TDD:

1) Escribir un test e intentar compilar, aunque aun no sea posible porque no se desarrollo la funcionalidad correspondiente.

2) Escribir la mínima cantidad de código posible para que la prueba unitaria compile y ejecutarla. Deberá fallar, ya que no tiene la funcionalidad requerida.

3) Escribir el test de tal manera, que al ser ejecutado, este no falle. La idea es que la función escrita no sea lo más elegante y optimo posible, sino que simplemente, permita pasar el test unitario exitosamente.

4) Refactorizar el código de la función, para que sea lo más optimo posible. Es muy posible que existan varias instancias de refactorización del código de la función en distintas etapas del proyecto.

5) Escribir un nuevo test unitario para otra funcionalidad.


Ventajas:

Todo muy lindo lo explicado, pero que ventajas concretas ofrece trabajar con TDD? Porque conviene trabajar con esta metodología?

  • En primer lugar y la ventaja más obvia, es que al tener el test unitario escrito por cada método, podemos detectar regresiones y cambios en una función que rompan otra parte del código.
  • Mayor facilidad para refactorizar y mantener código.
  • Si bien es verdad que TDD significa en un principio escribir más código, la realidad indica que muchas veces ahorra código y tiempo, al disminuir la cantidad de defectos en los métodos.
  • Al desarrollar el método de acceso al método antes que su implementación, ayuda a pensar cómo utilizar el componente y para que debería existir, por lo tanto, nos ayuda al diseño del software.

Recomendaciones:

  • Hacer más de un test unitario por función que contemplen distintas situaciones. Se deben probar la mayor cantidad de casos posibles. Es MUY recomendable hacer un test unitario negativo, o sea, que si no devuelve una excepción o el resultado de llamar al método es distinto de false, es porque hay un error. Por ejemplo, si es una regla de negocio, hacer un test unitario para que contemple la validación exitosa y otro test unitario, que llame al método con parámetros incorrectos para probar que la validación la está haciendo de manera correcta.
  • No meter lógica en los test unitarios. Si queremos probar distintas situaciones, usemos un test unitario por cada prueba.
  • No hacer que un test unitario dependa de otro test. Deben ser lo más independientes posibles uno del otro.

TDD vs TAC:

Uno de los componentes principales de TDD es Test First Development, que como ya dijimos antes, consiste en escribir el test unitario antes que el código. Pero sin embargo, existe otro enfoque más tradicional, que es precisamente el inverso. Escribir el test unitario DESPUES de haber escrito el código y a esto se lo llama Test After Coding (TAC).

Escribir antes el test unitario nos garantiza que todos los métodos y funciones de nuestra aplicación, tengan su respectivo test unitario. En cambio, esto en la práctica, no está garantizado con TAC, ya que siempre se va a estar corriendo con los tiempos.

Por otro lado, TDD nos ayuda a pensar el diseño del software, ya que cuando escribimos el test unitario, debemos pensar en la interfaz del método y como va a interactuar. Si empiezo diseñando la prueba, se hace más claro que tiene que hacer exactamente el método, con lo cual, se podría reducir tiempos y sobre todo, posibilidad de errores.

Otro punto a favor de TDD y muy importante, la primera vez que se ejecuta el test unitario, este debe fallar. Si el test al ser ejecutado, pasa exitosamente, es porque está mal escrito. Sin embargo, si el test fue escrito posteriormente al desarrollo del método, existe la posibilidad de no poder detectar que el test unitario en si mismo este mal escrito, ya que si siempre se ejecuta exitosamente, uno tiende a pensar que el método esta correcto y dificulta la tarea de ver si el test unitario está correcto. Esto es fundamental.

Ejemplo de código:

Pongamos un poco de TDD en acción y veamos un ejemplo simple pero práctico de cómo usar esta metodología de trabajo.

Supongamos que nos piden desarrollar modulo que permita insertar, modificar, listar y eliminar clientes. A su vez también tenemos que programar un método de validación que cumpla con la siguiente regla de negocio del cliente: Debe tener menos de 50 años, el sexo solo puede ser F o M, y el nombre es un campo obligatorio.

Para simplificar, vamos a usar el patrón Active Record (del cual ya habíamos hablado antes), que nos permite en una misma clase poner la lógica de negocio y de acceso a datos.

public class Cliente
{

private string nombre;
private int edad;
private string sexo;

public string Sexo
{

get { return sexo; }
set { sexo = value; }

}

public int Edad
{

get { return edad; }

set { edad = value; }

}

public string Nombre
{

get { return nombre; }
set { nombre = value; }

}

}

Ahora debemos crear la clase ClienteTest y para reducir el ejemplo, vamos a crear solamente 2 pruebas unitarias (Validar e Insertar)

  • 1° Fase

[TestClass()]

public class ClienteTest

{

[TestMethod()]

public void InsertarTest()

{

Cliente nuevo = new Cliente();

nuevo.Edad = 35;

nuevo.Sexo = “F”;

nuevo.Nombre = “Alejandra”;

bool resultado = Cliente.Insert(nuevo);

Assert.AreEqual(resultado, true);

}

[TestMethod()]

public void ValidarTest()

{

Cliente nuevo = new Cliente();

nuevo.Edad = 50;

nuevo.Sexo = “F”;

nuevo.Nombre = “Alejandra”;

bool resultado = nuevo.Validate();

Assert.AreEqual(resultado, true);

}

}

Obviamente este codigo no va a compilar, ya que los metodos de la clase Cliente todavia no fueron siquiera declarados.

  • 2° Fase

Ahora debemos crear en la Clase Cliente, los 2 metodos que vamos a utilizar, pero con el UNICO objetivo que las pruebas unitarias puedan compilar y fallar al ser ejecutadas.


///
<summary>

/// Valida que el el cliente cumpla con las reglas de negocio.

///</summary>

public bool Validate()

{

return false;

}

///<summary>

/// Inserta un nuevo Cliente en la base de datos.

///</summary>

public static bool Insert(Cliente nuevo)

{

return false;

}

  • 3° Fase

Corremos todos los test unitarios desarrollados y estos deben fallar (Red Status)


  • 4° Fase

Desarrollamos los 2 métodos correspondientes a los test unitarios (para el segundo método, no lo desarrolle completo para no hacer largo el código y clarificar lo que se quiere hacer).


public bool Validate()

{

if (edad < 50) return false;

if (String.IsNullOrEmpty(sexo)) return false;

if (!sexo.Equals(“F”) && !sexo.Equals(“M”)) return false;

if (String.IsNullOrEmpty(nombre)) return false;

return true;

}

public static bool Insert(Cliente nuevo)

{

try

{

using (SqlConnection conn = new SqlConnection(“CONNECTION_STRING”))

{

using (SqlCommand command = new SqlCommand(“INSERT INTO CLIENTES …”))

{

command.ExecuteNonQuery();

}

}

return true;

}

catch

{

return false;

}

}

  • 5° Fase

Corremos todos los test unitarios desarrollados y ahora estos deben pasar exitosamente (Green Status)


Links:

noviembre 22, 2008

Patrones de Acceso a Datos: Active Record

Filed under: .NET, Arquitectura, Capa de Datos, Patrones — Etiquetas: , — grimpi @ 10:12 pm

Existen varias estrategias, arquitecturas y patrones de diseño para el manejo de la lógica de negocio y el acceso a la base de datos.
Que determina el uso de una u otra arquitectura esta dado por la especificadores y características del software a desarrollar y también por el gusto de quien diseña el esqueleto de la aplicación (la subjetividad es un factor muy determinante en el desarrollo de software, muchas veces más que cualquier argumento racional).
Hoy vamos a ver uno de estos patrones: Active Record.
ActiveRecord es un patrón en el cual, el objeto contiene los datos que representan a un renglón (o registro) de nuestra tabla o vista, además de encapsular la lógica necesaria para acceder a la base de datos. De esta forma el acceso a datos se presenta de manera uniforma a través de la aplicación.
Lógica de Negocio + Acceso a Datos en una misma clase.
Una clase Active Record consiste en el conjunto de propiedades que representa las columnas de la tabla más los típicos métodos de acceso como las operaciones CRUD, búsqueda (Find), validaciones, y métodos de negocio.
Personalmente me gusta mucho este enfoque, es muy elegante y simple.

En que situaciones CONVIENE usar este patrón?

  • Lógica de negocio simple y poco relacionada con otras entidades.
  • Es ideal cuando la estructura de la tabla coincide con la estructura de la clase.

En que situaciones NO CONVIENE de Active Record:

  • Es simple. Esto es bueno y malo al mismo tiempo. Con lógica de negocio compleja, este patrón pierde coherencia.
  • Otra desventaja que al estar tan acoplado a la estructura de la clase, un cambio en el diseño de la tabla, implica cambiar la clase.
  • En situaciones de operaciones de alto volumen de datos, el overhead que se paga en el pasaje y carga de datos, es innecesario. Esta desventaja aplica tanto a Active Record, como a cualquier otro diseño orientado a objetos.
  • Muchos “puristas” de OOP critican que ActiveRecord tiene una misma clase tanto la responsabilidad de acceder a la base como de manejar la lógica de negocio y “ensucia” el código. Nunca coincidí con ese fundamentalismo que a veces prioriza el purismo por sobre la simplicidad, pero es algo que muchos critican de este patrón.

Ejemplo de una clase típica de Active Record:

public class Order
{

public Order()
{
}

public Order(int orderID)
{
}

public int OrderID {get; set;}
public DateTime OrderDate {get; set;}
public DateTime ShipDate {get; set;}
public string ShipName {get; set;}
public string ShipAddress {get; set;}
public string ShipCity {get; set;}
public string ShipCountry {get; set;}

public void Insert()
{
// Inserta un registro en la tabla
}

public void Delete()
{
// Elimina el registro de la tabla
}

public void Update()
{
// Modifica el registro en la tabla
}

public static int GetCount()
{
// Retorna el total de registros de la tabla
}

public static Order FindById(int id)
{
//Busca en la tabla el objeto usando como criterio su id.
}

public static List<Order> LoadAll()
{
// Carga todos los regisotros de la tabla.
}

}
Por lo general siempre existen algunos métodos estáticos, como por ejemplo LoadAll(), que devuelve una colección de objetos de la misma clase. Otro típico ejemplo de un método estático es el FindById()

Conversión de datos

Este es un problema habitual en cualquier metodología que haga un mapeo de datos de un objeto con la base de datos.
Por ejemplo, si tenemos en la tabla una columna de tipo int cuyo valor en un registro es nulo, como convertimos este valor dentro de una propiedad del objeto? Por un lado podemos usar Nullable<int> que viene a partir de .NET 2.0. Otra estrategia es transformar valores nulos en 0. Puede parecer un poco desprolija esta metodología, pero muchas veces hay que preguntarse si queremos identificar entre un campo numérico nulo y uno cuyo valor sea 0. Obviamente, si queremos poder distinguir entre estos 2 valores, deberemos usar Nullable<int>.

Foreign Key Mapping Pattern (FKM)

Supongamos con el ejemplo que vimos anteriormente, que la tabla Order, tiene una FK a la tabla Customer. Como se debe comportar una clase Active Record cuando la tabla con la que trabaja tiene una Foreing Key?
Existen 2 maneras:

En primer lugar, mantener lo mas purista y simple posible y agregar solamente una propiedad más que sea el CustomerId, de manera que la estructura del objeto sea idéntica a la estructura de la tabla.

public int CustomerID {get; set;}

La segunda opción, es usar el patron Foreign Key Mapper. En mi opinión, una opcion mucho mas clara mucho más clara que consiste en vez de agregar una propiedad que represente el ID de la tabla, agregar una propiedad que sea una referencia directa al objeto.

public Customer Customer {get; set;}

Row Data Gateway Pattern (RDG)

Row Data Gateway es un patrón exclusivamente orientado al acceso a la base de datos, pero de características similares a Active Record.
La gran diferencia entre ambos, es que Active Record incluye métodos de acceso a la base de datos y métodos de lógica de negocio, mientras que Row Data Gateway, solo incluye métodos de acceso a la base.

Frameworks y generadores de codigo

  • Castle ActiveRecord: Por lejos, el framework más común que para trabajar con Active Record en .NET. Esta implementado sobre nhibernate, por lo cual también hay que tener esta librería para poder usarlo.
  • LINQ to SQL: Es la solución desarrollada por Microsoft para el mapeo de objetos con la base de datos. Puede también usarse con el patrón Active Record.
  • Además de estos frameworks, existen herramientas de generación de código en base a este patrón como .netTiers. También uno se puede crear su propio template de generación de código con MyGeneration o CodeSmith.


abril 9, 2008

El patrón DAO

Filed under: .NET, Arquitectura, Patrones, SQL Server — grimpi @ 1:12 am

Yo no soy un fanático de implementar montón de patrones y capas a los sistemas. Hoy en día hay sistemas que para desarrollar una función, requieren escribir en hasta 7 archivos diferentes.
Muchas veces complica más las cosas de lo necesario.
Sin embargo, uno de los patrones en mi opinión “fundamentales” del desarrollo de software, es el DAO (Data Access Object).
El DAO maneja la conexión con la fuente de datos para obtener y almacenar datos.

El uso de este patrón ofrece varios beneficios para la persistencia de datos:
* Sirve para separar el acceso a datos de la lógica de negocio. Algo altamente recomendable en sistemas medianos o grandes, o que manejen lógica de negocio compleja.
* Encapsula la fuente de datos. Esto es especialmente beneficioso en sistemas con acceso a múltiples entradas.
* Oculta la API con la que se accede a los datos. Por ejemplo en .NET si usamos siempre OleDb y queremos cambiar al NHibernate.
* Centraliza Todos los Accesos a Datos en un Capa Independiente

Cuando trabajamos con DAO, trabajamos en un mundo donde desconectado, donde nuestros datos se deben persistir en objetos.
Por lo tanto, cuando nos piden realizar una operación, se abre la conexión a la base, se ejecuta el comando, si es una operación de lectura, se vuelca el contenido hacia una estructura de datos y se cierra la conexión.

Transacciones:
Un problema muy común cuando se trabaja con este patrón, es como usar las transacciones.
Se deben ejecutar todas las operaciones dentro de un método?
Puede un DAO invocar métodos en otro DAO? Quien y como se deben manejar las transacciones?
Bueno, todo esta problemática que define el límite de la transacción se llama “Transaction demarcation”.
Existen 2 modelos diferentes que aplican según la necesidad de la operación transaccional que se va a efectuar.
Uno hace al objeto DAO responsable de la transacción, el otro desplaza la transacción al objeto que esta llamando el método DAO.
El primer caso (y el más usual) se puede codificar de la siguiente manera en C#:

public class ProductoDAO
{
public void Eliminar()
{
SqlConnection connection = new SqlConnection(“CONEXION”));
conn.open();
using (SqlTransaction trans = new SqlTransaction(conn))
{
SqlCommand com = new SqlCommand(“DELETE FROM P1”, conn, trans);
com.Execute();
SqlCommand com2 = new SqlCommand(“DELETE FROM P2”, conn, trans);
com2.Execute();
SqlCommand com3 = new SqlCommand(“DELETE FROM P3”, conn, trans);
com3.Execute();
trans.Commit();
}

conn.close();
}

}

En este ejemplo, para eliminar todos los productos y tablas relacionadas, se llama al objeto que maneja las transacciones de ADO.NET.
Con lo cual es perfectamente lógico encapsular el borrado de todas las tablas en un mismo método DAO, ya que se está afectando solamente la entidad Producto.

Pero supongamos que queremos eliminar transaccionalmente además de todos los productos, todas las ventas y todas las agencias.
Seria correcto meter la lógica del borrado de Ventas y Agencias dentro del método EliminarProducto? Como funcionar, funcionaria obviamente, pero estaríamos mezclando operaciones de distintas entidades y peor aún, estaríamos agregando métodos poco reutilizables y confusos.
Entonces lo correcto en este caso sería meter la lógica de la transacción fuera del DAO.
Para poder implementar esto, es necesario usar Transacciones distribuidas que .NET se implementan con EnterpriseService (En Java existe JTA). En caso de que usemos SQL Server 2005, podemos simplificar su considerablemente.

public class Negocio

{
public static void EliminarTodo
{
using (TransactionScope scope = new TransactionScope())
{
VentasDAO.Eliminar();
AgenciasDAO.Eliminar();
ProductoDAO.Eliminar();
scope.Complete();
}
}
}

Pasaje entre capas:
Ahora bien, ya sabemos porque debemos usar DAO y que beneficios nos trae.
Pero como deben ser el pasaje de datos con esta capa? Qué tipo de estructura usar?
En .NET existen varias variantes:

1) Dataset (tipado o no tipado)
2) XML
3) Data Transfer Objects (o objetos de Entidad)

Dependiendo de las particularidades de la aplicación, una opción puede ser mejor que la otra.
Definitivamente en la mayoría de los casos, usar Dataset no es la mejor práctica. Estamos de acuerdo que tal vez sea la opción más “cómoda”, pero los dataset son objetos pesados y lentos (en comparación con un Datareader), tienen montón de información, propiedades y métodos que no necesitamos.
Pero lo peor no es eso, la principal desventaja es que fuerza a toda la aplicación a usar un objeto de una tecnología especifica.

La más común de todas, es usar los Data Transfer Objects (DTO), también conocido como Value Object (VO). Es el típico patrón asociado a DAO.
Que son los DTO? Bueno, no son más que simples objetos que solo tienen getters y setters, que sirven para transportar la información de una capa a otra. El usar este patrón, ganamos performance, ya que al ser objetos livianos ocupan menos memoria, además de que nos permite abstraernos de cualquier tecnología especifica.

Recomendaciones a la hora de implementar DAO:

1) Combinar con el patrón singleton:
Tiene sentido tener múltiples instancias de DAO en memoria?
En la mayoría de los casos no, por eso yo por lo menos, suelo declarar todas mis clases DAO como static.

2) Crear una interfaz y un factory:
Todos los DAO generalmente tienen métodos comunes a todos, como DeleteById, GetById, GetAll, etc.
Declarar una interfaz IDAO, nos permitiría hacer un factory de DAOs.

3) Evitar el uso de dataset:
Si, como dije antes, es cómodo usarlos, nos ahorran tiempo de desarrollo. Pero a un costo alto.

Links:
http://www.ibm.com/developerworks/java/library/j-dao/
http://www.miguelmatas.es/blog/2007/11/13/mejorando-nuestro-dao-y-dto/
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.htm

Crea un blog o un sitio web gratuitos con WordPress.com.