Java 让您的业务(服务)层依赖于用户会话是一种糟糕的设计吗?

Java 让您的业务(服务)层依赖于用户会话是一种糟糕的设计吗?,java,architecture,ejb,Java,Architecture,Ejb,在一个普通的MVC设计的应用程序中,让服务层依赖于用户会话是一个坏主意吗?假设有一个服务方法可以从数据库中获取一些对象,您希望根据初始化调用的人返回不同的结果-例如,管理员可能会得到10行对象,而普通用户可能只得到7行,因为最后3行是“仅限管理员”的对象。解决这一问题的几种方法是: 引入一个新的方法参数,其中包括调用用户。依赖性较小,但在许多方法中必须加入用户参数,这很麻烦 为不同的用户角色创建不同的方法(具有多个结果)。此外,依赖性较小,但有许多方法基本上做相同的事情,这增加了代码重复的风险

在一个普通的MVC设计的应用程序中,让服务层依赖于用户会话是一个坏主意吗?假设有一个服务方法可以从数据库中获取一些对象,您希望根据初始化调用的人返回不同的结果-例如,管理员可能会得到10行对象,而普通用户可能只得到7行,因为最后3行是“仅限管理员”的对象。解决这一问题的几种方法是:

  • 引入一个新的方法参数,其中包括调用用户。依赖性较小,但在许多方法中必须加入用户参数,这很麻烦
  • 为不同的用户角色创建不同的方法(具有多个结果)。此外,依赖性较小,但有许多方法基本上做相同的事情,这增加了代码重复的风险
  • 让该方法在存储当前用户会话的静态上下文中读取ThreadLocal变量。此变量在每次请求之前设置
最近,我开始越来越多地使用最后一种方法,因为它提供了一个干净的界面,使用起来非常实用。筛选器确保当前线程始终具有用户集。这个设计不好吗?我相信有些人会认为这是从服务层到web层的一种依赖性,尽管我个人认为它们是非常不耦合的。最大的结果是,一个方法的行为将根据另一个类的状态而有所不同,这可能是一件坏事,也可能是一件好事


你对此有何看法?如果这是一个糟糕的解决方案,那么哪一个更强大呢?

我强烈建议采用aginst ThreadLocal风格的方法-这似乎太像一种“全局变量”设计味道了。这有许多问题,最明显的是:

  • 当您在测试之前设置越来越多的隐式全局状态时,您的代码变得越来越难测试
  • 您将无法清楚地看到某段代码使用的参数。这可能会让维护人员非常困惑,或者如果您在几个月后回到代码中
  • 如果您将工作交给不同的线程,它可能会导致非常严重的错误/复杂性。您已经有效地使代码执行依赖于它在哪个线程上运行。。。。什么可能出错?:-)
  • 您正在创建循环依赖项(服务层用户界面层)。这绝对不是一个好主意,您应该尝试保持依赖关系只单向流动(几乎总是用户界面层->服务层)
在其他两种方法中,我认为“视情况而定”:

  • 如果用户本质上是数据模型的一部分(例如,您有一个社交图数据库),那么将用户作为参数传递似乎很自然
  • 如果用户数据只是用于前端的东西,如身份验证等,那么我倾向于最小化对特定用户详细信息的依赖,而是为不同的角色创建不同的方法(或者等效地添加一个“角色”参数)

另一种选择是将“上下文”对象传递给包含一组相关会话数据(即,不仅仅是用户名)的服务层。如果希望最小化参数膨胀,这可能是有意义的。小心它变成了一种“绕过”好的分层原则的方式。如果只是“数据”,那么它可能没问题,但一旦人们开始在上下文中传递回调对象/UI组件,那么您可能就有点乱了……

从架构的角度来看,我认为您的最后一个选项是唯一明智的

  • 存在安全风险。您将不得不在会话中(或通过任何其他安全机制)根据用户信息验证用户名,这实际上会使这可能具有的任何优势无效(因为您必须已经知道用户名/角色)

  • 它既不实用也不可扩展。假设您需要添加另一个需要更多信息(而不仅仅是更多记录)的角色。然后,您将拥有用于现有2个用户角色的方法和用于新角色的另一组方法,可能具有不同的签名和if逻辑来处理不同的场景。这往往会变得混乱,非常非常快,并导致严重的维护问题

  • 如果您研究Java中的web框架,您会注意到它们使用的是最后一种方法。他们还将用户信息放入一个更“抽象”的实体(身份、上下文等),并提倡在处理信息分层时使用角色而不是用户名。通过这种方式,更容易管理存在于更大用户群中的复杂性,其中有多个角色需要不同的信息视图。这只是将角色添加到用户配置文件中的问题

    作为参考,请参见Seam的实现:

    由于评论导致的更新没有足够的空间

    从下面的推荐中可以看出,您似乎正在将服务和UI层耦合起来。只有当您从未期望外部实体使用您的服务层时,才会发生这种情况。这可能没问题,这取决于你的情况

    除此之外,我同意米凯拉的观点,但只是在某一点上<代码>线程本地s不是全局的,因为静态值是全局的。对于您的服务层来说,用户信息当然是全局的,将其与标准业务信息分离是有意义的。还请记住,您可能需要存储其他此类信息。如果您需要用户的国家/地区、语言或货币,该怎么办

    ThreadLocal
    s提供了一种有效而高效的解决方案。不完美?当然不会,但如果正确实施,它会比其他解决方案更干净。尽管如此,确实如此
    @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();    
        }  
    }