将业务(服务)层依赖于用户会话是不好的设计?

时间:2012-09-03 11:35:04

标签: java architecture ejb

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

  • 介绍一个包含主叫用户的新方法参数。在很多方法中必须使用用户参数的依赖性较小但很麻烦。
  • 为不同的用户角色制作不同的方法(包含多个结果)。同样依赖于依赖性但很多方法基本相同,这增加了代码重复的风险。
  • 让方法从存储当前用户会话的静态上下文中的ThreadLocal变量中读取。在每个请求之前设置此变量。

最近,我开始越来越多地使用最后一种方法,因为它提供了一个干净的界面,并且使用起来非常实用。过滤器确保当前线程始终具有用户设置。这是不好的设计吗?我相信有些人可以将其视为从服务层到Web层的依赖,尽管我个人认为它们非常不相关。最大的后果是方法行为将根据另一个类的状态而有所不同,这可能既是坏事也是好事。

您对此有何看法?如果这是一个糟糕的解决方案,那会是一个更强大的解决方案吗?

4 个答案:

答案 0 :(得分:5)

我强烈建议使用aginst ThreadLocal风格方法 - 它看起来像是一个“全局变量”设计气味太多了。这有很多问题,最值得注意的是:

  • 当您在测试前设置越来越多的隐式全局状态时,您的代码会越来越难以测试
  • 无法清楚地看到某段代码正在使用的参数。对于维护人员来说,或者如果你在几个月后回到代码中,这可能会非常混乱。
  • 如果您将工作交给不同的主题,可能会导致非常讨厌的错误/复杂性。您已经有效地使代码执行依赖于它运行在哪个线程上......可能出现什么问题? : - )
  • 您正在创建循环依赖(服务层< - >用户界面层)。从来没有一个好主意,你应该尝试保持依赖只有一种方式(几乎总是用户界面层 - >服务层)

在其他两种方法之间,我认为“这取决于”:

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

另一种选择是将“上下文”对象传递到包含一组相关会话数据(即不仅仅是用户名)的服务层。如果您想最小化参数膨胀,这可能是有意义的。请注意它变成了一种“绕过”良好分层原则的方法。如果它只是“数据”那么它可能很好,但是一旦人们开始在上下文中传递回调对象/ UI组件,那么你可能会走向一点点......

答案 1 :(得分:3)

只使用Java EE的角色机制,它已经存在。

举个例子:

@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();    
    }  
}

在网络方面,请确保您正在执行container login,以便服务器知道已登录的用户。

答案 2 :(得分:2)

您不必为您的用例实施任何解决方案。

在EJB中,框架已经支持您所需的内容。它被称为安全上下文,它会自动传播到您调用的每个方法中(在幕后它确实经常由TLS实现,但这是一个实现细节)。

此安全上下文将允许您访问用户名和他/她的角色。在您的情况下,您似乎需要检查角色。

您可以通过在方法(@RolesAllowed)上使用注释,或者通过注入EJB会话上下文并向其询问当前用户的角色来以声明方式执行此操作。

答案 3 :(得分:1)

从架构的角度来看,我认为你的最后一个选择是唯一理智的选择。

  1. 存在安全风险。您最终必须根据会话中的用户信息(或通过任何其他安全机制)验证用户名,这有效地使这可能具有的任何优势无效(因为您必须已经知道用户的名称/角色)。 / p>

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

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

    作为参考,请参阅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

    由于评论而导致的更新空间不足

    根据您的推荐,您似乎正在将服务和UI层结合起来。只有在您不希望外部实体使用您的服务层时,才会发生这种情况。这可能没问题,这取决于你的情况。

    除此之外,我同意迈克拉的观点,但仅限于某一点。 ThreadLocal在静态值是全局的意义上不是全局的。对于您的服务层,用户的信息肯定是全球性的,将其与标准业务信息隔离是有意义的。还要记住,您可能还有其他需要存储的信息。如果您需要用户的国家/地区或语言或货币怎么办?

    ThreadLocal提供了一种有效且高效的解决方案。不完美?当然不是,但如果正确实施,它比其他解决方案更清洁。也就是说,它确实需要一些努力来构建一个适当且简单的实现,但几乎可以说任何事情。