【问题标题】:What libraries exist for multitenant/conditional configurations for Java?Java 的多租户/条件配置有哪些库?
【发布时间】:2012-05-16 15:04:40
【问题描述】:

我正在尝试寻找一种解决方案来配置服务器端 Java 应用程序,以便系统的不同用户与系统交互,就好像它的配置不同 (Multitenancy)。例如,当我的应用程序为来自 user1 的请求提供服务时,我希望我的应用程序以克林贡语响应,但对于所有其他用户,我希望它以英语回复。 (我故意选择了一个荒谬的例子,以避免细节:重要的是我希望应用程序针对不同的请求表现不同)。

理想情况下,有一个通用的解决方案(即允许我添加 用户特定覆盖到我的配置的任何部分,而无需更改代码)。

我查看了具有 built in support for multitenant configuration 的 Apache Commons 配置,但据我所知,这是通过将 一些基本配置 与 一些覆盖集。这意味着我将有一个配置指定:

application.lang=english

还有,比如 user1.properties 覆盖文件:

application.lang=klingon

不幸的是,如果我们的支持团队可以在一个地方查看所有相关配置,并且以某种方式内联指定覆盖,而不是为 base 和 覆盖 em>。

我认为 Commons Config 的多租户 + Velocity template 之类的组合来描述底层配置中的条件元素是我的目标 - Commons Config 便于与我的配置和 Velocity 交互在单个配置中表达地描述任何覆盖,例如:

#if ($user=="user1")
 application.lang=klingon
#else
 application.lang=english
#end

人们使用什么解决方案来解决这类问题?

【问题讨论】:

  • IMO 您的速度示例将更难处理。通常,人们使用应用程序/前端来编辑多个配置文件,例如资源/翻译文件。只在配置工具上花费少量精力,后端并不重要。
  • @Dave 但是如果我选择使用它作为我的后端,我仍然需要这样的东西来编写 工具?

标签: java configuration velocity apache-commons-config


【解决方案1】:

您可以像下面这样对每个服务器操作进行编码吗?

void op1(String username, ...)
{
    String userScope = getConfigurationScopeForUser(username);
    String language = cfg.lookupString(userScope, "language");
    int    fontSize = cfg.lookupInt(userScope, "font_size");
    ... // business logic expressed in terms of language and fontSize
}

(上面的伪代码假设用户名作为参数传递,但您可以通过另一种机制传递它,例如线程本地存储。)

如果以上可以接受,那么Config4* 可以满足您的要求。使用Config4*,上述伪代码中使用的getConfigurationScopeForUser()方法可以实现如下(这里假设cfg是一个Configuration对象,之前已经通过解析配置文件进行了初始化):

String getConfigurationScopeForUser(String username)
{
    if (cfg.type("user", username) == Configuration.CFG_SCOPE) {
        return Configuration.mergeNames("user", username);
    } else {
        return "user.default";
    }

}

这是一个与上述内容一起使用的示例配置文件。大多数用户从“u​​ser.default”范围获取他们的配置,但 Mary 和 John 对其中一些默认值有自己的覆盖:

user.default {
    language = "English";
    font_size = "12";
    # ... many other configuration settings
}

user.John {
    @copyFrom "user.default";
    language = "Klingon"; # override a default value
}

user.Mary {
    @copyFrom "user.default";
    font_size = "18"; # override a default value
}

如果上面听起来可能满足您的需求,那么我建议您阅读“入门指南”的第 2 章和第 3 章,以充分了解 Config4* 语法和 API,以便能够确认/驳斥 Config4* 是否适合您的需求。您可以在 Config4* website 上找到该文档。

免责声明:我是 Config4* 的维护者。

编辑:我正在提供更多细节以回应 bacar 的 cmets。

我没有将 Config4* 放在 Maven 存储库中。但是,使用捆绑的 Ant 构建文件构建 Config4* 是微不足道的,因为 Config4* 不 对第三方库有任何依赖。

在服务器应用程序中使用 Config4* 的另一种方法(由 bacar 的评论提示)与 Config4* 如下...

像下面的伪代码那样实现每个服务器操作:

void op1(String username, ...)
{
    Configuration cfg = getConfigurationForUser(username);
    String language = cfg.lookupString("settings", "language");
    int    fontSize = cfg.lookupInt("settings", "font_size");
    ... // business logic expressed in terms of language and fontSize
}

上面使用的getConfigurationForUser()方法可以实现如下伪代码所示:

HashMap<String,Configuration>  map = new HashMap<String,Configuration>();

synchronized String getConfigurationForUser(String username)
{
    Configuration cfg = map.get(username);
    if (cfg == null) {
        // Create a config object tailored for the user & add to the map
        cfg = Configuration.create();
        cfg.insertString("", "user", username); // in global scope
        cfg.parse("/path/to/file.cfg");
        map.put(username, cfg);
    }
    return cfg;    
}

这是一个与上述内容一起使用的示例配置文件。

user ?= ""; // will be set via insertString()
settings {
    @if (user @in ["John", "Sam", "Jane"]) {
        language = "Klingon";
    } @else {
        language = "English";
    }
    @if (user == "Mary") {
        font_size = "12";
    } @else {
        font_size = "10";
    }
    ... # many other configuration settings
}

我对这两种方法的主要cmet如下:

  1. 第一种方法(一个包含大量变量和范围的Configuration 对象)可能比第二种方法(许多Configuration 对象,每个对象都包含少量变量)使用的内存略少。但我的猜测是,这两种方法的内存使用量都将以 KB 或数十 KB 为单位,与服务器应用程序的整体内存占用量相比,这将是微不足道的。

  2. 我更喜欢第一种方法,因为单个Configuration 对象只初始化一次,然后通过只读lookup() 样式操作访问它。这意味着您不必担心同步访问Configuration 对象,即使您的服务器应用程序是多线程的。相反,如果您的服务器应用程序是多线程的,则第二种方法要求您同步对HashMap 的访问。

  3. lookup() 样式操作的开销大约是纳秒或微秒,而解析配置文件的开销大约是毫秒或几十毫秒(取决于文件的大小)。第一种方法只执行一次配置文件的相对昂贵的解析,并且在应用程序的初始化中完成。相比之下,第二种方法对配置文件执行“N”次相对昂贵的解析(对于“N”个用户中的每个用户一次),并且在服务器处理来自客户端的请求时会发生重复开销。对您的应用程序而言,这种性能下降可能是问题,也可能不是问题。

  4. 我认为易用性比易于实施更重要。所以,如果你觉得第二种方式更容易维护配置文件,那么我建议你使用那种方式。

  5. 在第二种方法中,您可能想知道为什么我将大多数变量放在命名范围 (settings) 中,而不是与“注入”user 变量一起放在全局范围中。我这样做的原因超出了您的问题范围:将“注入”变量与应用程序可见变量分开可以更轻松地对应用程序可见变量执行架构验证。

【讨论】:

  • 酷,谢谢。 +1以获得详细答案。我在您的其他一篇文章中遇到了 Config4* - 不过我不太确定如何让它工作。我不相信我能得到速度模板给我的表现力(例如,如果我只想改变一些更大结构中的一个子元素),但我会玩。是否有带有 config4* 的 Maven 存储库?
  • 稍微研究了一下,让我感到震惊的是,注入一个名为“用户”的变量(例如cfg.insertString("user", "John"))然后使用@if (user=="John") { language = "Klingon"; } @else { language = "English"; } 可能更容易管理并获得我的表现力后。 @Ciaran - 对遵循这种方法有什么看法?
  • @bacar:我已经编辑了我的答案以响应您的 cmets。
【解决方案2】:

通常用户配置文件会进入数据库,并且用户必须通过登录打开会话。用户名可能会进入 HTTPSession (=Cookies),并且在每次请求时,服务器都会获取用户名并可以从数据库中读取配置文件。舒尔,数据库可以是一些配置文件,如 joe.properties、jim.properties、admin.properties 等。

【讨论】:

  • 它不是一个 HTTP 服务器应用程序 - 这个答案并没有真正解决基于条件逻辑覆盖默认值的概念 - 您似乎只是建议加载一个完全独立的属性文件。
猜你喜欢
  • 2018-10-01
  • 2017-09-24
  • 1970-01-01
  • 2012-08-29
  • 1970-01-01
  • 2021-12-11
  • 2012-08-11
  • 1970-01-01
  • 2011-10-06
相关资源
最近更新 更多