El caché de ASP.NET en una arquitectura de tres capas
En los ejemplos que se pueden encontrar en la documentación de ASP.Net o en los tutoriales de www.asp.net se muestran ejemplos del uso del caché con datos en aplicaciones de una sola capa; dentro de una misma página ASP.NET se realizan las consultas a datos, su procesamiento y visualización.
Si bien es práctico en desarrollos de poco alcance, cuando se va a desarrollar una aplicación en un entorno empresarial es muy recomendable una arquitectura en al menos tres capas; Interfaz de usuario (UI), Lógica de Negocio (Business Logic, BL) y acceso a datos (Data Access, DA).
Si queremos emplear el caché global de datos en nuestra lógica de negocio observaremos que depende realmente de clases del interfaz de usuario pues el objeto Cache se obtiene o bien del objeto Page actual o bien del objeto HttpContext. No podemos usar estas clases en nuestra Lógica de Negocio porque se haría dependiente del interfaz empleado. La capa BL debería servirnos para cualquier tipo de interface, WindowsForms, aplicación de consola, etc.
Para desacoplar las clases de caché dependientes de ASP.NET de la lógica de negocio, os propongo el siguiente diseño, basado en el patrón de diseño Estrategia (Strategy Pattern) [GAM97] y un patrón Adaptador (wrapper) [GAM97] . Permite elegir varias estrategias de cache y el segundo abstrae el interfaz de System.Web.Caching.Cache . Para el acceso a datos del modelo conceptual se utilizan DataSets en vez de objetos DTO junto a una clase DAO (Direct Access Object) que realiza las consultas al gestor SqlServer.
El interfaz de usuario (UI) accede a la lógica de negocio a través de una fachada (Facade Pattern)[GAM97] que al carecer de estado es una clase de instancia única (Singleton Pattern)[GAM97]
Diagrama 1. Diagrama estático de clases del ejemplo Sample.TestCache
La página por defecto invoca con su método BindData() el enlace inicial de los datos a los controles visuales.
private void BindData()
{
FacadeData facade = FacadeData.getWithCacheStrategy();
GridView1.DataSource = facade.Employees;
GridView1.DataBind();
}
En el fragmento de código superior vemos cómo invocando el método getWithCacheSrtategy() se obtiene de forma implícita una instancia de la fachada de acceso a la lógica de negocio y se solicita que utilice una estrategia de caché. La implementación concreta de ICacheStrategy se establece en el fichero web.config.
<appSettings>
<add key=«CacheStrategyClass« value=«Samples.TestCache.CacheStrategyAspNet, Samples.TestCache«/>
</appSettings>
La fachada tiene otros dos métodos para obtener una instancia de si misma; getInstance(), que carece de estrategias de cache y getWithCacheStrategy(ICacheStrategy) que acepta desde código una estrategia de cache explícita.
public static FacadeData getWithCacheStrategy(ICacheStrategy ipd)
{
if (instance == null)
instance = new FacadeData();
CacheStrategy = ipd;
return instance;
}
public static FacadeData getWithCacheStrategy()
{
return getWithCacheStrategy(getInstanceCacheStrategy());
}
private static ICacheStrategy getInstanceCacheStrategy()
{
string strCacheStrategyClass =
WebConfigurationManager.AppSettings[«CacheStrategyClass»];
if (strCacheStrategyClass != null)
return (ICacheStrategy) Activator.CreateInstance(
Type.GetType(strCacheStrategyClass));
else
return null;
}
La fachada también actúa en como proxy a la hora de recuperar los empleados. Se hace bajo demanda y devuelve el dataset del caché en caso de que así se haya configurado o directamente del DAO.
public DataSet Employees
{
get
{
if (CacheStrategy != null)
{
if (CacheStrategy.getData(«cachedEmployees») == null)
CacheStrategy.setData(«cachedEmployees», EmployeeDAO.getEmployees())
return CacheStrategy.getData(«cachedEmployees»);
}
else
return EmployeeDAO.getEmployees();
}
}
El interface de ICacheStrategy para este ejemplo, es muy sencillo:
public interface ICacheStrategy
{
DataSet getData(string dataKey);
void setData(string dataKey, DataSet data);
}
La implementación para el caso de utilizar el caché de ASP.NET:
public DataSet getData(string dataKey)
{
HttpContext context = System.Web.HttpContext.Current;
return (DataSet) context.Cache[dataKey];
}
public void setData(string dataKey, DataSet data)
{
HttpContext context = System.Web.HttpContext.Current;
context.Cache.Remove(dataKey);
context.Cache.Add(dataKey, data, null, DateTime.Now.AddMinutes(MIN_TO_EXPIRE),
Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
El método setData podría haberse simplificado, sin embargo he preferido esta opción porque, entre otras cosas, se puede controlar el tiempo de invalidación del caché y su prioridad. La clase precisa del contexto HTTP para poder acceder al caché.
Si distribuímos en ensamblados las clases, en el del IU tendríamos las clases Default (página ASPX). En el ensamblado de la lógica de negocio estarían el resto de clases. Nótese que CacheStrategyAspNet tiene una dependencia indirecta a HttpContext.
Este ejemplo podría haberse realizado igualmente con objetos del modelo en vez de con DataSets.
Si ejecutamos la aplicación veremos que múltiples peticiones devuelven los mismos datos obtenidos en el mismo instante (columna RetrievedAt). Incluso cuando el cliente recarga la página. Solo cuando hace clic en “Refresh Data” se actualiza el caché. Recordad que este caché es privado a la aplicación en ejecución, se comparte entre todas las sesiones que mantenga la aplicación dentro del pool del servidor IIS, si por ejemplo queréis hacer una consulta con parámetros dependientes de la sesión este esquema no os valdría.
Con este ejemplo hemos visto cómo desacoplar la tecnología de caché de asp.net de una fachada de nuestra lógica de negocio.
Queda abierta la discusión para el caso de querer crear un cliente ligero y llevar la lógica de negocio a un servidor distinto al de IIS, usando por ejemplo, Windows Communication Foundation.
Referencias
[GAM97] Design Patterns, Elements of Reusable Object-Oriented Software, Gamma et Al. Addison-Wesley Eds. 1997. ISBN 0-201-63361-2