2012-09-03 20 views
9

En una aplicación común diseñada por MVC, ¿es una mala idea hacer que la capa de servicio dependa de una sesión de usuario? Supongamos que hay un método de servicio que obtiene algunos objetos de una base de datos y desea devolver resultados diferentes según quién inicialice la llamada; por ejemplo, un administrador puede obtener 10 filas de objetos, mientras que un usuario normal solo puede obtener 7 filas porque los últimos 3 fueron objetos "solo para el administrador". Algunas maneras de resolver esto serían:¿Tiene mal diseño hacer que su capa de negocios (servicio) dependa de una sesión de usuario?

  • Introduzca un nuevo parámetro de método donde incluya al usuario que llama. Dependencia, pero incómodo tener que agregar parámetros de usuario en muchos métodos.
  • Realice diferentes métodos para diferentes roles de usuario (con resultados múltiples). También sin dependencias pero con muchos métodos que básicamente hacen lo mismo, lo que aumenta el riesgo de duplicación de código.
  • Deja que el método lea desde una variable ThreadLocal en un contexto estático que almacena la sesión de usuario actual. Esta variable se establece antes de cada solicitud.

Últimamente, he comenzado a utilizar el último método cada vez más, ya que proporciona una interfaz limpia y se siente muy práctico para trabajar. Un filtro se asegura de que el hilo actual siempre tenga un conjunto de usuarios. ¿Es este mal diseño? Creo que algunos podrían ver esto como una dependencia de la capa de servicio a la capa web, aunque personalmente creo que están bastante desacoplados. La mayor consecuencia es que el comportamiento de los métodos será diferente dependiendo del estado de otra clase, lo que podría ser malo y bueno.

¿Qué piensas de esto? Si es una mala solución, ¿cuál sería más fuerte?

Respuesta

5

Recomiendo encarecidamente los enfoques de estilo de ThreadLocal: parece demasiado un olor de diseño de "variable global". Esto tiene muchos problemas, sobre todo:

  • Su código obtiene más difícil de probar a medida que avanza por la pendiente resbaladiza de tener más y más implícita estado global para establecer antes de la prueba
  • Usted pierde la capacidad para ver claramente con qué parámetros está trabajando un determinado fragmento de código. Esto puede ser muy confuso para los mantenedores o si vuelve al código después de unos meses.
  • Puede causar errores muy desagradables/complejidad si alguna vez entrega el trabajo a diferentes hilos. Efectivamente ha hecho que la ejecución de su código dependa de en qué hilo se ejecuta ... ¿qué puede salir mal? :-)
  • Está creando dependencias circulares (capa de servicio < -> capa de interfaz de usuario). Nunca es una buena idea, usted debe tratar de mantener las dependencias que fluye en una sola dirección (casi siempre de usuario capa de interfaz -> capa de servicio)

Entre los otros dos enfoques, creo que "depende":

  • Si el usuario es parte intrínseca del modelo de datos (p. Ej., Tiene una base de datos de gráficos sociales), parecería natural pasar al usuario como parámetro.
  • Si los datos del usuario solo se utilizan para elementos frontales como autenticación, etc.entonces, tendería a minimizar la dependencia de los detalles específicos del usuario y, en su lugar, crear diferentes métodos para diferentes roles (o, de manera equivalente, agregar un parámetro de "rol").

Una opción alternativa es pasar un objeto de "contexto" a la capa de servicio que contiene un conjunto de datos de sesión relevantes (es decir, algo más que el nombre de usuario). Esto podría tener sentido si quiere minimizar la saturación de parámetros. Solo ten cuidado de que se convierta en una forma de "moverte" por buenos principios de superposición. Si solo son "datos", entonces probablemente esté bien, pero tan pronto como la gente comience a pasar objetos de devolución de llamada/componentes UI en el contexto, entonces probablemente te estés volviendo loco ...

+0

Puntos muy válidos, relacionados con cosas que me preocupaban. –

+0

> parecería natural pasar al usuario como un parámetro. ¿¿¿Por qué??? EJB ya le proporcionó acceso a los roles del usuario y a la declaración del problema que es todo lo que necesita OP. –

+0

@dexter - seguro de que tendría sentido si está utilizando EJB para roles de usuario/seguridad - la pregunta no especifica esto sin embargo. – mikera

1

Desde la perspectiva de la arquitectura, Creo que tu última opción es la única sana.

  1. Presenta un riesgo de seguridad. Usted terminaría teniendo que validar el nombre de usuario contra la información del usuario en sesión (oa través de cualquier otro mecanismo de seguridad), lo que efectivamente invalida cualquier ventaja que esto pueda tener (ya que de todos modos debería saber el nombre/rol del usuario).

  2. No es realmente práctico ni escalable. Imagine que necesita agregar otro rol que requiera más información (no solo más registros). Entonces tendría métodos para sus 2 roles de usuario existentes y otro conjunto para la nueva función, probablemente con una firma diferente y if-if-if lógica para manejar los diferentes escenarios. Eso tiende a ser complicado, muy, muy rápido y conduce a graves problemas de mantenimiento.

Si observa frameworks web en Java, observará que utilizan el último enfoque. También colocan la información del usuario en una entidad más "abstracta" (identidad, contexto, etc.) y defienden el uso de roles en lugar de nombres de usuario cuando se trata de la estratificación de la información. De esta forma, es mucho más fácil administrar la complejidad que existe en las bases de usuarios más grandes, donde hay múltiples roles que requieren diferentes vistas de información. Solo se trata de agregar roles a los perfiles de usuario.

Como referencia, ver la implementación de Seam: http://grepcode.com/file/repository.jboss.org/nexus/content/repositories/releases/org.jboss.seam/jboss-seam/2.0.0.GA/org/jboss/seam/contexts/Contexts.java#Contexts.

actualización debido a comentar que no están teniendo suficiente espacio

Desde su commend a continuación, parece que son la conexión entre el servicio y la interfaz de usuario de capa, a continuación. Eso solo puede suceder si nunca espera que las entidades externas consuman su capa de servicio. Eso puede estar bien, solo depende de tu situación.

Aparte de eso, estoy de acuerdo con Mikera, pero solo hasta cierto punto. ThreadLocal s no son globales en el sentido de que un valor estático es global. Para su capa de servicio, la información del usuario es ciertamente global y tiene sentido segregarla de la información comercial estándar. También recuerde que puede tener otra información similar que necesita ser almacenada. ¿Qué sucede si necesita el país, el idioma o la moneda del usuario?

ThreadLocal s presentan una solución efectiva y eficaz. ¿No es perfecto? Seguramente no, pero es algo más limpio que otras soluciones si se implementa correctamente. Dicho esto, se requiere un poco de esfuerzo para construir una implementación adecuada y segura, pero eso se puede decir de casi cualquier cosa.

+0

1. En realidad, no es un riesgo de seguridad; el método solo se llama localmente, por lo que tengo control total sobre qué roles pueden llamar a qué métodos. 2. Estoy de acuerdo con. ¿Qué piensas acerca de las preocupaciones de Mikeras en su respuesta? Creo que tiene algunos puntos válidos. –

+0

@RasmusFranke: consulte la respuesta actualizada ya que el cuadro de comentarios no tiene espacio suficiente para dar cabida a mi respuesta. – lsoliveira

2

No tiene que implementar ninguna solución para su caso de uso.

En EJB, lo que desea ya es compatible con el marco.Se llama contexto de seguridad y se propaga automáticamente en cada método que usted llame (detrás de las escenas, de hecho, a menudo es implementado por TLS, pero eso es un detalle de implementación).

Este contexto de seguridad le dará acceso al nombre de usuario y sus roles. En su caso, todo lo que parece necesitar es verificar los roles.

Puede hacerlo declarativamente utilizando anotaciones en sus métodos (@RolesAllowed), o inyectando el contexto de la sesión EJB y pidiéndole las funciones del usuario actual.

+0

Idea interesante, aunque en mi caso tengo algunos metadatos del usuario que se necesitan, así como los roles. Debería haber mencionado esto más claramente en mi pregunta. El sistema de seguridad en mi aplicación es un poco más fino, permitiendo roles para objetos dinámicos. –

+0

@rasmusfranke bien, por lo que depende exactamente de qué es esta metadata. También tendrá acceso al nombre de usuario a través del contexto de seguridad, y posiblemente se pueda usar como clave para recuperar estos metadatos (por ejemplo, desde un DB o caché explícito como Infinispan). Depende de sus requisitos exactos si este método es factible o no. –

+0

Es cierto, pero prefiero introducir un parámetro de método adicional en lugar de tener que hacer una lectura de base de datos. Especialmente cuando el método del cliente que llama ya tiene esta información. –

2

Simplemente use el mecanismo de roles de Java EE, ya está allí.

A modo de ejemplo:

@Stateless 
public class AService { 

    @Resource 
    private SessionContext sessionContext; 

    @PersistenceContext 
    private EntityManager entityManager; 

    public List<AObject> fetchAFewObjects() { 

     String query = "aUserQuery";  

     if (sessionContext.isCallerInRole("ADMIN") 
      query = "aAdminQuery"; 

     return entityManager.createNamedQuery(query, AObject.class) 
      .getResultList();  
    } 
} 

En el lado web, asegúrese de que está haciendo un container login, por lo que el servidor es consciente de que el usuario que ha iniciado sesión en.

Cuestiones relacionadas