user7379885
user7379885

Reputation:

How to manage two sets of properties at runtime?

I'm building a web service that needs to switch between two sets of properties depending on the request URL coming in. I'm not sure which is the best method of handling this.

I've got a Spring Boot app that has an yaml properties file. Inside the properties file the structure looks something like this;

optionA:
  foo:
    urls:
      - a
      - b
  bar:  
    something: hello

optionB:
  foo:
    urls:
      - x
      - y
  bar:  
    something: bye

Both optionA and optionB have pretty much all the same properties, just different values.

So a request comes in, I check the request and decide if I need optionA or optionB.

I've been trying to get @ConfigurationProperties to handle this but the properties are initialised on startup so it can't be dynamic. Another possibility is that I have two Configuration classes, one for each option but then my code gets full of checks to switch between the two classes and the classes are pretty much identical, not really nice either.

Any best practices or recommendations on how to best manage this would be appreciated, cheers!

Upvotes: 11

Views: 1121

Answers (3)

Patrick
Patrick

Reputation: 12754

If you have not too many options I would go this way: (Just made example with smaller config)

options.yml:

optionA:
  name: optionA
optionB:
  name: optionB

I created a Option class for extension:

public class Option {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

And two Option classes where the @ConfigurationProperties are getting set: (For now these classes are empty but you have the opportunity to be more specific on each different option)

@Component
@ConfigurationProperties(prefix ="optionA", locations = "classpath:options.yml")
public class OptionA extends Option{
}
@Component
@ConfigurationProperties(prefix ="optionB", locations = "classpath:options.yml")
public class OptionB extends Option{
}

For the decision of the different options I created an interface:

public interface OptionService {

    Option findOption(boolean businessLogic);
}

And in the implementation I inject both options and the implementation of the business logic: (in an easy way)

@Service
public class OptionServiceImpl implements OptionService {

    private OptionA optionA;
    private OptionB optionB;

    @Override
    public Option findOption(boolean businessLogic) {
        if(businessLogic){
            return getOptionA();
        } else {
            return getOptionB();
        }
    }

    public OptionA getOptionA() {
        return optionA;
    }

    @Autowired
    public void setOptionA(OptionA optionA) {
        this.optionA = optionA;
    }

    public OptionB getOptionB() {
        return optionB;
    }

    @Autowired
    public void setOptionB(OptionB optionB) {
        this.optionB = optionB;
    }
}

And at the end your controller just call the OptionServiceImpl class and deceide which option should be used:

@Controller
public class YourController {

    private OptionService optionServiceImpl;

    @RequestMapping("/")
    public String getIndex(){
        Option option = getOptionServiceImpl().findOption(true);
        System.out.println(option.getName());
        option = getOptionServiceImpl().findOption(false);
        System.out.println(option.getName());

        return "Hello World";
    }

    public OptionService getOptionServiceImpl() {
        return optionServiceImpl;
    }

    @Autowired
    public void setOptionServiceImpl(OptionService optionServiceImpl) {
        this.optionServiceImpl = optionServiceImpl;
    }
}

Output of System.out.println:

optionA
optionB

So your business logic to decide which option should be used is not an if - else construct. You are able to create the rules for the decission in the interface and its implementation. I think you are able to create more rules for more controllers.

Upvotes: 2

Walery Strauch
Walery Strauch

Reputation: 7072

Change your yml to:

options:
  - name: optionA
    foo:
      urls:
        - a
        - b
    bar:  
      something: hello
  - name: optionB
    foo:
      urls:
        - x
        - y
    bar:  
      something: bye

Add Config class:

@Data
@ConfigurationProperties
@Configuration
public class MyConfig {

    private List<Option> options;
}

Use it:

@Component
public class UseConfig {

    @Autowired
    public UseConfig(final MyConfig config) {
        System.out.println(config.getOptions());
    }

}

Result:

[Option(name=optionA, foo=Foo(urls=[a, b]), bar=Bar(something=hello)), Option(name=optionB, foo=Foo(urls=[x, y]), bar=Bar(something=bye))]

Upvotes: 1

Barath
Barath

Reputation: 5283

You can define key value pairs in application.properties.

where key is web service name and value is option(list of properties)

Make use of @ConfigurationProperties

@ConfigurationProperties 
class Configuration {

    Map<String,Option> options; 

    // getters and setters
}

@Component
class ChooseServiceBasedConfiguration {

    @Autowired
    Configuration configuration;

    public void serviceMethod(String key ){
        //get appropriate properties of the web service
        configuration.getOptions().get(key);
    }    
}

based on web service get the values required using the key .

Upvotes: -1

Related Questions