Service Pack 2 para Windows 2003 Server en español

Desde ayer teneis listo para descargar el Service Pack 2 de 2003 server por fin en castellano:


http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=1b9fe9e4-1d57-4698-a5cf-db271ed6d90a


Oh, si alguno tiene un ISA montado que eche un ojo, por si acaso a:


WARNING! Windows Server 2003 SP2 May Destroy Your ISA Firewall without Warning:


http://blogs.isaserver.org/shinder/2007/03/23/warning-windows-server-2003-sp2-may-destroy-your-isa-firewall-without-warning/


You cannot host TCP connections when Receive Side Scaling is enabled in Windows Server 2003 with Service Pack 2:


http://support.microsoft.com/default.aspx?scid=kb;EN-US;927695


 

Desacoplando System.Web.Cache de nuestra lógica de negocio

Normalmente la tarea que más aumenta el tiempo de respuesta en la devolución de una página ASP.NET que consulta contra una BD es la propia consulta a la BD. Un escenario frecuente es la recuperación y presentación de datos con poca tasa de actualización. Para cada cliente que ejecuta la página se ejecuta de nuevo la consulta. Pese a que internamente SQL Server u otro gestor SGBD suele tener planificada las últimas consultas SQL realizadas y dispone de sus propios cachés, no tenemos la certeza de que así sea, sobre todo en momentos de alta carga de consultas, justo cuando más se necesita.


La primera aproximación para mejorar el throughput contra la BD es hacer las consultas solo en la primera petición de la página, en post backs sucesivos no se realiza la consulta.


    protected void Page_Load(object sender, EventArgs e)


    {


        if (!Page.IsPostBack)


        {


            BindData();


        }


    }


El método BindData() obtendría los datos de la BD y, por ejemplo, se los asociaría a un GridView , en peticiones posteriores  de la página por parte del mismo cliente no se ejecutaría este método, pero se seguirían mostrando los datos en el viewstate.  Este codelet significó una gran mejora de ASP.NET 1.0 frente ASP. En ASP.NET 1.0 se introdujo ya un conjunto de clases para gestionar un caché de datos y su política de invalidación. ASP.NET 2.0 mejora y extiende estas características.


Caché de datos global


Supongamos un escenario en el que tenemos una serie de datos en la BD relativamente inmutables y que utilizan en común todos los clientes de nuestra aplicación ASP.NET 2.0. Por ejemplo, una tabla de provincias o de empleados. Con la técnica del apartado anterior, cada vez que un nuevo cliente solicita o recarga la página que contiene un combo con la lista de provincias se ejecuta al menos una consulta contra la BD.


Puesto que los datos son comunes a todos los clientes de la aplicación podemos usar el caché de datos global. Este caché, a menos que se especifique lo contrario, tiene el mismo tiempo de vida y validez que el proceso de nuestra aplicación ASP.NET en el servidor. La afinidad del caché es a nivel de aplicación en un pool de IIS. Por lo que en un entorno de webfarming se tendría un caché global para cada aplicación en un pool dentro de un nodo del clúster.


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


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