Reputation: 485
A related group of applications I've been assigned share a database table for configuration values that has a table with columns 'application', 'config_name', 'config_type' (IE String, Integer), and 'config_value'. There's also a stored procedure that takes in a string (applicationName) and returns all config names, types, and values where applicationName == application.
In each application, a wrapper class is instantiated which contains a static ThreadLocal (hereafter 'static config)', and that static config pulls all values from the config table for the application.
When loading configuration values, the stored procedure returns a massive list of properties that are iterated over, going through a massive list of if-else statements testing whether the 'config_name' column matches a string literal, and if so, loads the value into a differently named variable.
EX:
if (result.isBeforeFirst()) {
while(result.next()) {
if (result.getString("config_name").equals("myConfig1") {
myConfigurationValue1 = result.getString(config_value); }
else if (result.getString("config_name").equals("myConfig2") {
myConfigurationValue2 = result.getString(config_value); }
}}
These cover between 60-100ish configs per app, and each application has an identical Configuration class save for the names of the properties they're trying to read.
So my questions are:
Having one gigantic configuration class is poor design, right? I'm not entirely sure how to break them down and I can't share them here, but I'm assuming best practice would be to have multiple configuration classes that have all the components needed to perform a particular operation, IE 'LocalDatabaseConfig' or '(ExternalSystemName)DatabaseConfig'?
Once broken down, what's the best way to get config valuee where needed without static access? If I have each class instantiate the configuration it needs I'll be doing a lot of redundant db operations, but if I just pass them from the application entry point then many classes have to 'hold on' to data they don't need to feed it to later classes... Is this a time when static config classes are the best option??
Is there an elegant way to load properties from the DB (in core java - company is very particular with third party libraries) without using this massive if-else chain I keep thinking that ideally we'd just dynamically load each property as it's referenced, but the only way I can think to do that is to use another stored procedure that takes in a unique identifier for a property and load it that way, but that would involve a lot more string literals...m
(Might be invalidated by 3) Is there a better way for the comparison in the pseudo-code above to test for a property rather than using a string literal? Could this be resolved if we just agreed to name our configuration properties in the application the same way they're named in the DB?
Currently every application just copy-pastes this configuration class and replaces the string literals and variable names; many of the values are unique in name and value, some are unique in value but are named the same between applications (and vice versa), and some are the same name and value for each application, but because the stored procedure fetches values based on application, redundant db entries are necessary (despite that many such values are supposed to be the same at all times, and any change to one needs to be performed on the other versions as well). Would it make sense to create a core library class that can construct any of the proposed 'broken down' configuration classes? IE, every application needs some basic logging configurations that don't change across the applications. We already have a core library that's a dependency for each application, but I don't know whether it would make sense add all/some/none of the configuration classes to the core library...
Thanks for your help! Sorry for the abundance of questions!?
Upvotes: 1
Views: 566
Reputation: 15156
You would be better off separating the database access from the application initialisation. A basic definition would be Map<String,String>
returned by querying for one application's settings:
Map<String,String> config = dbOps.getConfig("myappname");
// which populates a map from the config_name/config_value queries:
// AS: config.put(result.getString("config_name"), result.getString("config_value");
Your application code then can initialise from the single application settings:
void init(Map<String,String> config) {
myConfigurationValue1 = config.get("myConfig1");
myConfigurationValue2 = config.get("myConfig2");
}
A benefit of this decoupling is that you define test cases for your application by hardwiring the config
for different permutations of Map settings without accessing a huge test database configurations, and can test the config loader independently of your application logic.
Once this is working, you might consider whether dbOps.getConfig("myappname")
caches the per-application settings to avoid excessive queries (if they don't change on database), or whether to declare Config
as a class backed by Map but with calls for getInt
/ get
and default values, and which throw RuntimeException
if missing keys:
void init(Config config) {
myConfigurationValue1 = config.get("myConfig1", "aDefaultVal");
myConfigurationInt2 = config.getInt("myConfig2", 100);
}
Upvotes: 1
Reputation: 2244
The cascading if-then-else
might be eliminated by using a while
loop to copy the database-query results into two maps: a Map[String, String]
for the string-based configuration variables, and a Map[String, Integer]
for the integer configuration variables. Then the class could provide the following operations:
public String lookupStringVariable(String name, String defaultValue) {
String value = stringMap.get(name);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public int lookupIntVariable(String name, int defaultValue) {
Integer value = intMap.get(name);
if (value == null) {
return defaultValue;
} else {
return value.intValue();
}
}
If there is a requirement (perhaps for runtime performance) to have the configuration values stored in fields of the configuration class, then the configuration class could make the above two operations private
and use them to initialize fields. For example:
logLevel = lookupIntVariable("log_level", 2);
logDir = lookupStringVariable("log_dir", "/tmp");
An alternative (but complementary) suggestion is to write a code generator application that will query the DB table and generate a separate Java class for each value in the application
column of the DB table. The implementation of a generated Java class would use whatever coding approach you like to query the DB table and retrieve the application-specific configuration variables. Once you have written this generator application, you can rerun it whenever the DB table is updated to add/modify configuration variables. If you decide to write such a generator, you can use print()
statements to generate the Java code. Alternatively, you might use a template engine to reduce some of the verbosity associated with print()
statements. An example of a template engine is Velocity, but the Comparison of web template engines Wikipedia article lists dozens more.
Upvotes: 1