Какие библиотеки существуют для мультитенантных / условных конфигураций для Java?

Я пытаюсь найти решение для настройки серверного Java-приложения таким образом, чтобы разные пользователи системы взаимодействовали с системой, как если бы она была настроена по-разному (Multitenancy). Например, когда мое приложение обслуживает запрос от пользователя user1, я хочу, чтобы мое приложение отвечало на клингонском языке, но для всех остальных пользователей я хочу, чтобы оно отвечало на английском языке. (Я выбрал заведомо абсурдный пример, чтобы избежать конкретики: важно то, что я хочу, чтобы приложение вело себя по-разному для разных запросов).

В идеале существует универсальное решение (то есть такое, которое позволяет мне добавлять пользовательские переопределения к любой части моей конфигурации без изменения кода).

Я ознакомился с конфигурацией Apache Commons, в которой встроена поддержка многопользовательской конфигурации, но насколько я могу судить, это делается путем объединения некоторой базовой конфигурации с некоторым набором переопределений. Это означает, что у меня будет конфигурация, в которой указано:

application.lang=english

и, скажем, файл переопределения user1.properties:

application.lang=klingon

К сожалению, нашей группе поддержки будет намного проще, если они смогут видеть все связанные конфигурации в одном месте с переопределениями, определенными как-то встроенными, вместо того, чтобы иметь отдельные файлы для базовых и переопределений.

Я думаю, что какая-то комбинация мультитенантности Commons Config + что-то вроде шаблона скорости для описания условные элементы в базовой конфигурации - это своего рода то, к чему я стремлюсь - Commons Config для простоты взаимодействия с моей конфигурацией и Velocity для очень выразительного описания любых переопределений в одной конфигурации, например:

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

Какие решения используют люди для такого рода проблем?


person bacar    schedule 16.05.2012    source источник
comment
ИМО, с вашим примером скорости было бы намного сложнее иметь дело. Обычно люди используют приложение / интерфейс для редактирования нескольких файлов конфигурации, например, файлов ресурсов / переводов. Потратьте лишь незначительное количество энергии на инструмент настройки, и серверная часть не имеет особого значения.   -  person Dave Newton    schedule 16.05.2012
comment
@Dave Но мне все равно понадобится что-то вроде этого, чтобы написать tool, если бы я решил использовать его в качестве серверной части?   -  person bacar    schedule 17.05.2012


Ответы (2)


Допустимо ли для вас кодировать каждую операцию сервера, как показано ниже?

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 является объектом конфигурации, который был ранее инициализирован путем анализа файла конфигурации):

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

}

Вот пример файла конфигурации для работы с вышеуказанным. Большинство пользователей получают свою конфигурацию из области "user.default", но Мэри и Джон имеют свои собственные переопределения некоторых из этих значений по умолчанию:

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 «Руководства по началу работы», чтобы получить достаточно хорошее понимание синтаксиса и API Config4 *, чтобы иметь возможность подтвердить / опровергнуть пригодность Config4 * для ваших нужд. Вы можете найти эту документацию на веб-сайте Config4 * .

Отказ от ответственности: я сопровождаю Config4 *.

Изменить: я предоставляю более подробную информацию в ответ на комментарии bacar.

Я не помещал Config4 * в репозиторий Maven. Однако собрать Config4 * с помощью связанного файла сборки Ant тривиально, потому что 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
}

Основные комментарии, которые у меня есть по поводу двух подходов, заключаются в следующем:

  1. Первый подход (один Configuration объект, содержащий множество переменных и областей видимости), вероятно, потребует немного меньше памяти, чем второй подход (много Configuration объектов, каждый с небольшим количеством переменных). Но я предполагаю, что использование памяти при любом подходе будет измеряться в килобайтах или десятках килобайт, и это будет незначительно по сравнению с общим объемом памяти вашего серверного приложения.

  2. Я предпочитаю первый подход, потому что один объект Configuration инициализируется только один раз, а затем к нему осуществляется доступ через операции в стиле lookup() только для чтения. Это означает, что вам не нужно беспокоиться о синхронизации доступа к объекту Configuration, даже если ваше серверное приложение является многопоточным. Напротив, второй подход требует, чтобы вы синхронизировали доступ к HashMap, если ваше серверное приложение является многопоточным.

  3. Накладные расходы на операцию в стиле lookup() составляют, скажем, наносекунды или микросекунды, в то время как накладные расходы на синтаксический анализ файла конфигурации составляют, скажем, миллисекунды или десятки миллисекунд (в зависимости от размера файла). . Первый подход выполняет этот относительно дорогой анализ файла конфигурации только один раз, и это делается при инициализации приложения. Напротив, второй подход выполняет этот относительно дорогой анализ файла конфигурации «N» раз (один раз для каждого из «N» пользователей), и эти повторяющиеся расходы возникают, пока сервер обрабатывает запросы от клиентов. Это снижение производительности может быть или не быть проблемой для вашего приложения.

  4. Я считаю, что простота использования важнее, чем простота реализации. Итак, если вы чувствуете, что второй подход упростит обслуживание файла конфигурации, я предлагаю вам использовать этот подход.

  5. При втором подходе вы можете задаться вопросом, почему я поместил большинство переменных в именованную область видимости (settings), а не в глобальную область видимости вместе с «введенной» переменной user. Я сделал это по причине, выходящей за рамки вашего вопроса: разделение «внедренных» переменных от переменных, видимых для приложения, упрощает выполнение проверки схемы для переменных, видимых для приложения.

person Community    schedule 16.05.2012
comment
Хорошо, спасибо. +1 за подробный ответ. Я наткнулся на Config4 * в одном из ваших постов - хотя я не совсем знал, как заставить его работать. Я не уверен, что получаю выразительность, которую дал бы мне шаблон скорости (например, если я хочу изменить только один подэлемент в какой-то гораздо более крупной структуре), но я сыграю. Есть ли репозиторий maven с config4 *? - person bacar; 17.05.2012
comment
Немного посмотрев на это, я понимаю, что введение переменной с именем 'user' (например, cfg.insertString("user", "John")), а затем использование @if (user=="John") { language = "Klingon"; } @else { language = "English"; }, может быть даже проще в управлении и получить выразительность, которая мне нужна. @Ciaran - есть ли какие-нибудь мнения о таком подходе? - person bacar; 17.05.2012
comment
@bacar: Я отредактировал свой ответ в ответ на ваши комментарии. - person Ciaran McHale; 18.05.2012

Обычно профили пользователей попадают в БД, и пользователь должен открыть сеанс с логином. Имя пользователя может входить в HTTPSession (= Cookies), и при каждом запросе сервер будет получать имя пользователя и может читать профиль из БД. Shure, БД может быть некоторыми конфигурационными файлами, такими как joe.properties, jim.properties, admin.properties и т. Д.

person PeterMmm    schedule 16.05.2012
comment
Это не приложение HTTP-сервера - и этот ответ на самом деле не касается концепции переопределения значений по умолчанию на основе условной логики - вы просто предлагаете просто загрузить полностью отдельный файл свойств. - person bacar; 16.05.2012