Grimpi IT Blog

Marzo 11, 2009

Como almacenar passwords en una base de datos?

Archivado en: 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)

Archivado en: 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

Archivado en: .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

Archivado en: .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

Blog de WordPress.com.