owner icon indicating copy to clipboard operation
owner copied to clipboard

Nested configuration interfaces

Open jiri-neuman opened this issue 9 years ago • 6 comments

Hi, I've just came across your project and I really like it. I'd like to use it in our project and I have one question. Is it possible to create a configuration interface with additional nested interfaces.

An example of the idea:

public interface AppConfig {
    DatabaseConfig db();
}

public interface DatabaseConfig {

    String url();

    String user();

    String password();
}

And the corresponding properties configuration:

db.url=localhost
db.user=admin
db.password=pass

I would expect to pass the AppConfig to the ConfigFactory.create() and get the whole structure populated, including the nested interface. In the current version (1.0.8) this does not seem to work. Am I doing something wrong or is this not supported? Do you think it makes sense to implement this in a future version?

jiri-neuman avatar Jul 13 '15 17:07 jiri-neuman

Why would you need nested configuration for this case? For your configuration you can have one interface and three keys/methods like this:

   @Key("db.url")
   String url();

Perhaps, you have some more difficult use case in mind? Anyway.. you could achieve the desired effect by doing something like this:

@Sources({"file:db.properties"})
interface AppConfig extends Config {
    @DefaultValue("db")
    @ConverterClass(DatabaseConfigConverter.class)
    DatabaseConfig db();
}
@Sources({"file:db.properties"})
public interface DatabaseConfig extends Config {
    @Key("${db}.url")
    String url();

    @Key("${db}.user")
    String user();

    @Key("${db}.password")
    String password();
}
public class DatabaseConfigConverter implements Converter<DatabaseConfig> {
    @Override
    public DatabaseConfig convert(Method method, String input) {
        Map<String, String> imports = new HashMap<String, String>();        
        imports.put("db", input);
        DatabaseConfig dbConfig = ConfigFactory.create(DatabaseConfig.class, imports);
        return dbConfig;
    }
}
public class Main {
    public static void main(String... args) {
        AppConfig appConfig = ConfigFactory.create(AppConfig.class);
        DatabaseConfig dbConfig = appConfig.db(); 
        System.out.println("url: " + dbConfig.url());
        System.out.println("user: " + dbConfig.user());
        System.out.println("password: " + dbConfig.password());
    }
}

bodunov avatar Jul 14 '15 08:07 bodunov

Indeed, I had a more complex case in mind. When you have larger configuration with many section, it's actually better to split into multiple interfaces and not having tens or hundreds of methods in one interface. My example was shortened just to explain what I want to achieve.

Thanks a lot for your suggestion, I believe this principle can be successfully used to achieve the desired behavior with just a small overhead code. From my point of view, it still would be nice if this is the default behavior without the need to add a custom Converter, but I am happy to have a solution with the current version.

jiri-neuman avatar Jul 14 '15 10:07 jiri-neuman

The proposed solution is good when you don't know the name of some property (section). But if all properties' names are known beforehand and you just want to make a logical split into multiple interfaces, then you could use following approach:

@Sources({"file:app.properties"})
interface MyDbConfig extends Config {
   @Key("db.url")
   String url();

   @Key("db.user")
   String user();

   @Key("db.password")
   String password();
}

@Sources({"file:app.properties"})
interface MyFileConfig extends Config {
   @Key("file.path")
   String file();

   @Key("file.extension")
   String extension();
}

@Sources({"file:app.properties"})
interface MyAppConfig extends MyDbConfig, MyFileConfig {    
}

public class Main {
    public static void main(String... args) {
        MyAppConfig appConfig = ConfigFactory.create(MyAppConfig.class);
        System.out.println("url: " + appConfig.url());
        System.out.println("user: " + appConfig.user());
        System.out.println("password: " + appConfig.password());
        System.out.println("file: " + appConfig.file());
        System.out.println("extension: " + appConfig.extension());
    }
}

Getting rid of multiple @Sources's was already proposed in some other thread.

bodunov avatar Jul 14 '15 10:07 bodunov

I'm sorry for going a bit off topic here, but I could not find any thread explaining how to get rid of multiple @Sources. Any hints?

drapostolos avatar Jul 17 '15 20:07 drapostolos

There is no thread which explains how to actually get rid of multiple @Sources. There is just a thread where this issue was brought up and considered to be doable: https://github.com/lviggiano/owner/issues/128

bodunov avatar Jul 17 '15 20:07 bodunov

I'm sorry for going a bit off topic here, but I could not find any thread explaining how to get rid of multiple @Sources. Any hints?

Have you seen #145 - would you like giving some feedback there?

ketan avatar Dec 16 '15 13:12 ketan