Leer variables Session desde una capa de Negocios

En una reciente conversación en los foros de ms, desempolvé una vieja utilidad que en más de una ocasión me ha salvado de un apuro y que, aunque no tenía en el olvido, reconozco que no le di la importancia que tenía hasta que comprendí que mucha gente tiene los mismos problemas que tengo yo. Al fin y al cabo siempre pienso que soy más torpe que los demás y que lo que a mí me cuesta mucho rato desarrollar, el resto lo resuelven a la primera. Bueno, intento comentarlo con un ejemplo, que siempre es más ilustrativo:4

Una aplicación con dos capas y media (realmente son tres, pero como no viene al caso, intento abstraerme de la capa de datos). Una capa de presentación separada físicamente de la capa de negocios, es decir, en proyecto diferente. Con una aplicación a medio crear, de buenas a primeras nos damos cuenta que será mejor almacenar en tablas quién es el usuario que ha tocado el registro y cuál es la fecha de dicho cambio. Con respecto a la fecha no existe mucho problema pero si nos fijamos en el nombre del usuario apreciamos que está en la capa de presentación y no llega a la capa de negocios. ¿Qué hacemos en este caso?. Una de las opciones que no nos gusta a ningún desarrollador es la de modificar todos los procedimientos de datos, incluyendo el nombre del usuario en ellos. Con esta opción, seguro que nos dejamos alguna página o procedimiento sin actualizar…

¿Qué opción debemos tomar entonces?. Algo tan sencillo como hacer uso de la variable Session desde la capa de negocios. En algún foro creo haber leído el concepto de “Encapsular las variables Session” o “Añadir Intellisense a las variables de Session”. No sé cuál sería la nomenclatura adecuada pero creo que ambas tienen su parte de razón. Veamos el código:

   1: /// <summary>
   2: /// Obtiene o establece el nombre del usuario actual.
   3: /// </summary>
   4: public static string UserWeb
   5: {
   6:     get
   7:     {
   8:         if (System.Web.HttpContext.Current.Session["userName"] == null)
   9:             System.Web.HttpContext.Current.Session["userName"] = "unknown";
  10:         return System.Web.HttpContext.Current.Session["userName"].ToString();
  11:     }
  12:     set { System.Web.HttpContext.Current.Session["userName"] = value; }
  13: }

Si esta propiedad la introducimos dentro de la capa de negocios (se sobreentiende que en un proyecto diferente de la de presentación) podremos conseguir un “intellisense” de las variables de Session a la vez que estamos “encapsulando” las variables de sesión. Se cual sea su nomenclatura, nos servirá para poder tener una misma variable desde la capa de presentación como desde la capa de negocios que, al fin y al cabo era el objetivo buscado.

9 thoughts on “Leer variables Session desde una capa de Negocios

  1. Buenas Jesús!
    Si este código está dentro de la propia capa de presentación como “helper” para ofrecer intellisense a la sesión, no tengo nada que decir.
    Pero si este código está en la capa de lógica de negocio entonces, desde mi punto de vista, se está cometiendo un error brutal: la capa de negocio nunca debe tener referencias a NINGUNA tecnología de presentación concreta. Aquí estás ligando tu capa de lógica a la presentación web. No te “huele” mal una referencia a System.Web desde una capa de negocio? El acceso a las características propias de la API de presentación usada, debe limitarse a la capa de presentación.
    Me da a mi, que en este caso que mencionas, se ha partido de un mal diseño (por la razón que sea) de la capa de lógica y que después cuando ha surgido el nuevo requerimiento, en lugar de modificar adecuadamente la capa de lógica se ha cortado por lo sano y se ha accedido a la sesión.

    Dices en el post que una de las razones para no realizar este refactoring en la capa de negocio es que “seguro que nos dejamos alguna página o procedimiento sin actualizar”. Ese argumento deja traslucir una falta de pruebas (unitarias y de integración) que efectivamente convertirán cualquier refactoring (no sólo este) de la capa de negocio en un auténtico infierno.

    Un saludo! 😉

  2. Por el título del Post no tengo duda de que se está accediendo a este código desde la capa de negocio… y esto hace que esté de acuerdo con Eduard en un 1000%.

    Tener una aplicación dividida en capas no es crear una solución con varios proyectos. Capas significa responsabilidades bien definidas, tareas asignadas a cada capa según su definición.

    Te pongo un ejemplo concreto…

    – Si mañana yo necesito crear una aplicación Windows, un servicio o una aplicación de consola y que estas usen tu misma capa de negocio, tendrías un serio problema.

    – Si mañana necesitas exportar mediante una capa de servicios (WCF o Web Services) parte de la lógica de tu aplicación, tendrías un problema

    – Si mañana necesitas que tus capas pasen a ser Niveles para poder distribuir tu aplicación en varios servidores, tendrías un problema.

    No hay que temerle al Refactoring, no somos perfecto y siempre se nos puede quedar algo sea por la razón que sea. Los parches, siempre terminan creando una solución poco estable y dificil de mantener. Por eso me sumo al comentario de Eduard en cuanto a los test unitarios, cuando tienes toda tu lógica controlada por pruebas, nunca te da miedo tocar algo, te atreves con todo.. 😉

    Un salu2

  3. Teneis razon. Poner una referencia a system.web en capa de negocio es feo pero si se organizan bien las cosas puede quedar aceptable.

    Si me permitis os dejo un link a pastebin con un trozo de codigo de un ejemplo de arquitectura para utilizar vbles de sesion en el resto de capas de un sistema utilizando una fabrica que se encarga de inyectar la dependencia con la info de la sesion al construir la instancia.

    http://pastebin.com/ZZYVQjiR

    Obviamente no habria que usar una cadena a cascoporro como nombre de vble de sesion, eso ya entra en escribir un gestor de vbles de sesion fuertemente tipado (o usando genericos que queda muy bonico), tambien hay que hacer mas gestion de errores, etc. Bueno… que os lo tomeis como un ejemplo superficial 🙂

  4. Ni siquiera mirando el ejemplo de Pastebin estoy de acuerdo en la utlización de variables de Session en la capa de negocio..

    Mirando el enlaces que pones, veo que están creando o intentando crear un Singleton.. para el caso de WEB el static (C#) o Shared (VB) no vale, por la concurrencia (multithread) y lo resuelven usando variables de Session.. (cosa que tampoco haría)

    Aquí te dejo una clara explicación del patrón Singleton (MSDN) con un ejemplo de implementación de un Factory (Fabrica).

    http://msdn.microsoft.com/es-es/library/bb972272.aspx

    Salu2

  5. Estoy de acuerdo con Omar, no recomendaría acceder a elementos de una capa superior desde una inferior ya que esto generaria dependencia y lo que se quiere es crear elementos independientes.

  6. Hmmm. Creo que la situacion no es exactamente igual a como Omar comenta.

    El problema aqui no es el singleton y como implementarlo en un entorno web. El patron singleton es una solucion en algunos casos, no el problema en si.

    El problema es pasar informacion, obtenida desde una capa, al resto de las capas de la arquitecura. Esta informacion se mantendra sin cambios para la sesion de trabajo del usuario (web o escritorio) pero sera diferente para otras sesiones de trabajo de otros usuarios.

    En un entorno de escritorio la solucion es un patron sigleton, pero en un entorno web, un singleton (aunque sea thread safe y con Factory) no nos vale. La unica manera que tenemos de diferenciar al usuario en un entorno web es por su sesion y, por lo tanto, los datos deben estar relacionados con la sesion.

    Una solucion sin acoplamiento es pasar esa informacion desde la capa que no le queda mas remedio usar system.web al resto de las capas como parametro; pero eso significa tener un monton de funciones en las otras capas que acepten un parametro, que digamos queda “feo” junto con el resto de parametros de la funcion que son mucho mas relevantes y descriptivos para la tarea que realice la funcion. Una solucion mejor seria que, para las clases de las capas que realizan los procesos oportunos, se utilizase el contructor para pasarle una unica vez por vida del objeto los datos de sesion y asi esos procesos pueden tomar las decisiones adecuadas consultando su atributo inicializado en el constructor. Esto te ahorra poner en todas las funciones el parametro “feo”. Si se sigue analizando, pasar de este segundo planteamiento a una fabrica que se encargue de instanciar las clases e inyectarles la dependencia a los datos de sesion es un paso natural. Y pasar de esto ultimo a utilizar plantillas genericas y herencia es mas que obvio. El unico sacrificio que hay que hacer es una muy pequeña, controlada y muy localizada dependencia a system.web en la fabrica (y solo si se compila la fabrica para un entorno web), que no en las capas de negocio o acceso a datos.

    En mi opinion el codigo del enlace es aceptable (y por consiguiente utilizar system.web fuera del code behind de una aplicacion web). Por un lado, con las vbles de precompilacion nos quitamos el problema de diferentes archivos de codigo fuente de las fabricas con diferentes implementaciones en diferentes entronos(siempre se podria extender a mas entornos). Por otro lado, el uso de una fabrica que se encarga de crear los objetos de las otras capas inyectandole la dependencia, que inevitablemente tienen, de los datos de sesion del usuario reduce el acoplamiento a system.web(aunque sigue existiendo) a su minima expresion. Y por ultimo, la utilizacion de la herencia y las plantillas genericas, junto con la fabrica en si, hace que sea facilmente modificable y extensible y con pocas posibilidades de introduccion de errores.

    Como puse en el comentario anterior, es un ejemplo basico y habria que maquearlo;quizas usando alguna interfaz, alguna clase abstracta, controlando que algunas funciones no puedan ser sobreescritas en herencia, etc. Pero creo que en general la solucion es aceptable.

  7. Crowley,
    En la solución que tu comentas, lo que se ha implementado es un sistema de almacén de datos por usuario (la clase DataAccessFactory). En entornos web lo mapeas a Session y en entornos escritorio queda mapeado a algo static.
    En mi opinión esto es infraestructura, no negocio, en tanto desde las capas de negocio no accederás a Session sino que accederás a ese “almacén de datos por usuario”. Así pues, tu uso yo lo veo legítimo en tanto esto esté aislado de la capa de negocio.
    Siempre hablamos de las 3 capas básicas (presentación, lógica, datos) pero nos olvidamos de la capa transversal de infraestructura, que es donde se ubican esos mecanismos.

    Y efectivamente a partir de ahí todo es dar un paso más: usar una interfaz e inyectar la clase correspondiente (según el entorno “real”) a las capas de negocio.

    En mi opinión tu caso y el que plantea Jesús no son comparables, ya que él si que accede a la sesión desde negocio mientras que tu no. Y ahí radica la diferencia.

    Saludos!

  8. Tienes razon cuando dices que si que existe una diferencia entre lo mio y lo de Jesus.
    Pero yo he intentado interpretar este post de una manera mas… “profunda” digamos y no me quedo con lo basico de “acceder desde negocio y tirar millas”. Me quedo con la idea de que con una referencia al contexto Http que mantiene .NET puedes acceder a vbles de sesion y ahorrarte un monton de parametros feotes en un gran numero de funciones. Esa es la idea que importa y, como tal, yo he expuesto la misma solucion que Jesus pero de una forma mas currada para que la gente que se ha puesto taliban (sin ofender, chicos 😉 ) sobre incluir una referenca a system.web para acceder a la session fuera del contexto de ASP.NET propiamente dicho vea que, segun mi opinion, no solo es una tecnica que puede ser valida, si no que es bastante util.

    Saludos.

    PD: ¿Se nota mucho que me gusta discutir y filosofar sobre estos temas? 😛

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *