viernes, julio 14, 2006

Lazy Load usando delegates


Hace mucho un par de meses, nos vimos en la necesidad de implementar nuestro Lazy Loading a mano.
Hay diferentes formas de hacerlo, la mejor explicación que he leído es la de fowler. En su libro Patterns of Enterprise Application Architecture viene más detallado.

Bueno lazy load tiene 4 formas de ser implementado, Virtual Proxy, Ghost, Lazy Initialization y Value Holder. Frameworks de ORM como NHibernate lo manejan muy bien y de una manera casi transparente, pero en este caso no pudimos usar ningún framework de esos, por lo que tuvimos que usar muchos de los patrones que fowler describe en su libro.

Hizimos una combinación de Lazy Initialization con Virtual Proxy.

La diferencia principal con los ejemplos que da fowler, es que nosotros desde el business object, en este caso un cliente, no teníamos acceso a la capa de acceso a datos, ni siquiera a una interfaz, por lo que todo el truco fue usar un delegado. Esto último nos permitio también la posibilidad de reutilizar este mismo VirtualProxy para otras plataformas, la primera va y saca la información de una base de datos local y la otra va a sacarla de un servidor que tiene un esquema distinto, pero ambos mapean las columnas a las propiedades del business object de la misma forma.

Bueno resumidamente el diseño quedo así:
Tenemos un ClienteCoporativo que es una lista de clientes y al igual que las otras dos clases, tambien implementa ICliente, aparte de permitirnos iterar sobre la colección de clientes, tiene propiedades que nos regresan los acumulados, por ejemplo saldos.
ICliente define las propiedades y metodos que debe tener la clase.
Cliente es nuestro business entity.
ClienteLazy es nuestro Virtual Proxy y ahí está la parte interesante.

Ahora, para que es útil hacer un Lazy Loading. Bueno cuando cargamos de la base de datos información de una colección de clientes, nos interesa tener al menos el nombre y la descripción, lo cual podemos hacer directamente.

Ahora se presentan diferentes escenarios:
1. Con la lista de cliente simplemente mostramos estos al usuario quien podra seleccionar alguno de la lista y trabajar sobre uno en partícular, por lo que hasta aquí no existe la necesidad de crear completo todos los demas objetos cliente de la lista, simplemente hasta el momento en que se seleccione alguno nos terminamos de traer su información completa.
2. Seleccionaron algo como ver todos, por lo que hasta este momento es cuando necesitamos cargar toda la información en todos los objetos.
3. Simplemente resulta información suficiente el nombre y descripción, por lo que esto términa la interacción con el usuario.

El cargar un entity de este tipo se puede volvier pesado, si se considera que hay muchos objetos relacionados con este, como saldos, pudiera haber facturas, consignatarios, etc y no queremos traernos toda una gráfica compleja de objetos sino hasta el momento en que se necesite. Por otro lado no queremos que el usuario del business entity tenga que estar al pendiente de si se cargo o no ya el objeto, por ejemplo:
if(cliente.Facturas==null)
cliente.Facturas = FacturasDataAccess.GetFacturas(cliente.Clave);
cliente.Facturas.DoSomething()

Ok es enfadoso tener que estar haciendo esas validaciones, pero lo de menos es que sea enfadoso, lo malo es que es propenso a tener errores en tiempo de ejecución cuando se nos llega a olvidar (NullReferenceExcepction). Por otro lado, pudieramos no tener disponible FacturasDataAccess en todo momento. Si tenemos que hacer este tipo de validaciones pierde completamente el sentido de que el cliente tenga una propiedad Facturas si siempre que se vaya a usar debemos estar al tanto de como podríamos obtenerla de ser necesario.

Ok, así es que se vuelve impractico el querer cargar todo a la primera y se vuelve enfadoso el tener que validar y cargar las propiedades en el momento que las queramos usar.

Nuestro ClienteLazy es simplemente un wrapper para un Cliente.
De tal forma que podemos cargar nuestra colección con puros IClientes (que son más ligeros y rápidos de cargar) y cuando a un ICliente le piden algo como
Corporativos[0].Saldo
El ICliente verifica si ya tiene creado un objeto cliente con información real y de no tenerlo va y lo busca, lo crea y luego delega en el cliente para regresar la información. Los accesos subsecuentes ya no requieren ir a traerse nuevamente el cliente.

Ok, así es como lo define Fowler, como hicimos nosotros?

Como ClienteLazy y en general toda la capa de Business Objects y Business Entities no tenía acceso a la(s) capa(s) de DataAccess, tuvieron que usar un delegate para que este supiera como obener el cliente.

Este es parte del código de ClienteLazy

private Cliente c;
private GetCliente Get;
public ClienteLazy(GetCliente delegado, string clave, string nombre)
{
this.clave = clave;
this.nombre = nombre;
this.Get = delegado;
}
private void Check()
{
if (c == null)
c = Get(clave);
}


public string FormaDePago
{
get
{
Check();
return c.FormaDePago;
}


public string Nombre
{
get
{
return this.nombre;
}
}


Esta es la definicion del delegado, que marca la firma para el metodo.
public delegate Cliente GetCliente(string clave);

Si se fijan, al momento de acceder a forma de pago, vamos y nos aseguramos de tener al cliente listo y luego accedemos a su propiedad, pero al momento de regresar el nombre simplemente usamos nuestra variable local. La parte mas interesante esta en que el metodo Check usa el delegado para obtener al cliente.

Asi que los pasos son los siguientes.
Tenemos una clase (Virtual Proxy) que recibe en el constructor, la clave, nombre y otras propiedades que ustedes quieran dejar accesibles sin necesidad de ir a traerse el cliente completo. Tienen un delegado (la parte mas importante) que sabe que como crear un cliente, y por ultimo implementamos una interfaz común para hacer lucir al Virtual Proxy como el objeto real y al implementarla simplemente encapsulamos las propiedades del objeto real verificando en cada acceso que ya lo tengamos listo.

No hay comentarios.: