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!
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.
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))]
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 .
Related
Initial situation
I'm currently building an API with Spring using the library PipelinR, which is inspired by the famous NuGet package MediatR. I've created multiple packages within this application to isolate the java classes. The entrypoint of the API is in the package com.example.project.WebApi. The configuration file for the pipeline is also located here.
#Configuration
public class PipelinrConfiguration {
#Bean
Pipeline pipeline(ObjectProvider<Command.Handler> commandHandlers, ObjectProvider<Notification.Handler> notificationHandlers, ObjectProvider<Command.Middleware> middlewares) {
return new Pipelinr()
.with(commandHandlers::stream)
.with(notificationHandlers::stream)
.with(middlewares::orderedStream);
}
}
Anyways all the commands and command handlers are in different packages, like com.example.project.ApplicationService.CreateSomethingCommand.
com.example.project.ApplicationService.CreateSomething/
CreateSomethingCommand.java
CreateSomethingCommandHandler.java
Does anybody knows how I could provide these classes in my PipelinrConfiguration.java file, so that the ObjectProvider is able to find those.
I highly appreciate any kind of help, cheers!
Edit: #001
Yes, the beans are annotated with #Component.
CreateSomethingCommand.java
public class CreateSomethingCommand implements Command<Voidy> {
public String host;
public CreateSomethingCommand() {
}
public CreateSomethingCommand(String host) {
this();
this.host = host;
}
}
CreateSomethingCommandHandler.java
#Component
public class CreateSomethingCommandHandler implements Command.Handler<CreateSomethingCommand, Voidy> {
#Override
public Voidy handle(CreateSomethingCommand command) {
System.out.println("Command recieved by " + command.host);
return null;
}
}
#Configuration
#ComponentScan(basePackages = {"package1”, "package2"})
public class PipelinrConfiguration {
// attention here you have to declare three different beans of type ObjectProvider otherwise it will inject by type
#Bean
Pipeline pipeline(#Qualifier(“bean1”) ObjectProvider<Command.Handler> commandHandlers, #Qualifier(“bean2”) ObjectProvider<Notification.Handler> notificationHandlers, #Qualifier(“bean3”) ObjectProvider<Command.Middleware> middlewares) {
return new Pipelinr()
.with(commandHandlers::stream)
.with(notificationHandlers::stream)
.with(middlewares::orderedStream);
}
}
Implemented a strategy pattern very simple with the help of Spring Boot:
I have an interface:
public interface IOneStrategy {
void executeTheThing();
}
I have an implementation of the strategy One like this:
#Service("FIRST")
public class OneStrategyFirst implements IOneStrategy {
#Override
public void executeTheThing() {
System.out.println("OneStrategyFirst.executeTheThing");
}
}
I have a class which consumes the injected implementations:
#Service
public class ExecuteStrategyOne {
private Map<String, IOneStrategy> strategies;
public void executeStrategyOne(String name) {
if (!strategies.containsKey(name)) {
throw new IllegalArgumentException("The key " + name + " does not exist.");
}
strategies.get(name).executeTheThing();
}
}
and those implementations will be injected by Spring boot automatically by using the name FIRST, 'SECOND' etc. (assuming that this is simply a String etc. works very well.).
But now I want to implement another strategy via second interface:
public interface ITwoStrategy {
void executeTheThing();
}
and the executing service for the strategy:
#Service
public class ExecuteStrategyTwo {
private Map<String, ITwoStrategy> strategies;
...
}
and now the problematic part, because my application uses the same name which should be made part of the key of the above map I want to use the following:
#Service("FIRST")
public class TwoStrategyFirst implements ITwoStrategy {
#Override
public void executeTheThing() {
System.out.println("TwoStrategyFirst.executeTheThing");
}
}
This will of course result into an exception based on the duplicate bean name. The name FIRST is really needed to make the difference between the implementation.
I already found things about #Qualifier which I could use instead of #Service(FIRST)
#Service
#Qualifier(FIRST)
public class TwoStrategyFirst implements ITwoStrategy {
#Override
public void executeTheThing() {
System.out.println("TwoStrategyFirst.executeTheThing");
}
}
which unfortunately does not inject the classes into the map by using the name of the qualifier just by the name of the class which is not what I intended to do.
Does exist a solution to get the key of the map in the strategy execution the same as with the #Service("FIRST")?
I could use a solution via using the Qualifier annotation like this:
#Service
#Qualifier(FIRST)
public class TwoStrategyFirst implements ITwoStrategy {
...
}
And based on that there is a more or less easy solution via a bit of code:
#Service
public class ExecuteStrategyTwo {
private Map<String, ITwoStrategy> strategies;
public ExecuteStrategyOne(List<ITwoStrategy> strategies) {
this.strategies = strategies.stream()
.collect(
Collectors.toMap(k -> k.getClass().getDeclaredAnnotation(Qualifier.class).value(), Function.identity()));
}
This will inject all implementation into the list of the constructor and will be translated into the map by using the qualifier annotation.
Background:
I am working on a java Spring REST microservice that needs to work with multiple identical back-end systems and multiple identical databases depending on the request parameters.
Basically I have 3 "brands". For each brand there is a set of downstream services and a database. I have no control over those.
My spring service will receive brand as a part of request and will need to call the right downstream services and use the correct database.
Previously I would deal with this by having a separate instance of the spring service for each of the brands. There would be a single property file for each brand and spring would use it to wire up beans. I would have separate URL's for each brand and there was no problem.
Some of my beans need to know about "brand" during creation as they are wrappers around connections downstream services. I.e. once the bean is created there won't be a way to switch it to be a "different brand".
Problem:
I would like to change this so that a single instance of my service can handle requests for any brand.
Requirements:
I was thinking about the following solution:
Have a general property file for non-branded stuff. Spring would wire any non-branded beans and keep them as singleton beans.
Have a property file with brand specific urls etc for each of the brands
Spring would create set of singleton beans for each of the brand using appropriate property file.
Next when the request comes in spring would read the request params and use bean specific for that brand.
Performance is important to me so I would like to reuse the beans as much as possible.
I would like to make this thing as transparent as possible so that people creating new beans don't have to worry about doing anything outside standard configuration/context class.
Does anyone know what would be the best solution to achieve this?
I think you can solve the problem injecting the service in every request with the right set of configurations and beans; possibly already existing in your Application Context.
Given:
$ curl http://localhost:8080/greetings/rodo && echo
Hi from brand1, rodo
$ curl -H "x-brand-name: brand1" http://localhost:8080/greetings/rodo
Hi from brand1, rodo
$ curl -H "x-brand-name: brand2" http://localhost:8080/greetings/rodo && echo
Hi from brand2, rodo
The following code would work:
-- application.yml --
brand1:
greetingPrefix: Hi from brand1,
brand2:
greetingPrefix: Hi from brand2,
-- DemoApplication.java --
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Configuration
class ServiceConfig {
#Bean
public GreetingService greetingServiceBrand1(Brand1Config config) {
return new GreetingService(config);
}
#Bean
public GreetingService greetingServiceBrand2(Brand2Config config) {
return new GreetingService(config);
}
}
#Configuration
class WebConfig implements WebMvcConfigurer {
#Autowired
private ApplicationContext applicationContext;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(greetingServiceResolver());
}
private GreetingServiceResolver greetingServiceResolver() {
GreetingService greetingServiceBrand1 = applicationContext.getBean("greetingServiceBrand1", GreetingService.class);
GreetingService greetingServiceBrand2 = applicationContext.getBean("greetingServiceBrand2", GreetingService.class);
return new GreetingServiceResolver(greetingServiceBrand1, greetingServiceBrand2);
}
}
}
#RestController
#RequestMapping("/greetings")
class GreetingController {
#GetMapping("/{name}")
public String get(GreetingService greetingService, #PathVariable String name) {
return greetingService.sayHi(name);
}
}
class GreetingServiceResolver implements HandlerMethodArgumentResolver {
private final GreetingService greetingServiceBrand1;
private final GreetingService greetingServiceBrand2;
public GreetingServiceResolver(GreetingService greetingServiceBrand1, GreetingService greetingServiceBrand2) {
this.greetingServiceBrand1 = greetingServiceBrand1;
this.greetingServiceBrand2 = greetingServiceBrand2;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(GreetingService.class);
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory
) throws Exception {
String brand = nativeWebRequest.getHeader("x-brand-name");
return resolveGreetingService(brand);
}
private GreetingService resolveGreetingService(String brand) {
if ("brand2".equals(brand)) {
return greetingServiceBrand2;
}
return greetingServiceBrand1; // default
}
}
class GreetingService {
private BaseConfig config;
public GreetingService(BaseConfig config) {
this.config = config;
}
public String sayHi(String name) {
return config.getGreetingPrefix() + " " + name;
}
}
abstract class BaseConfig {
private String greetingPrefix;
public String getGreetingPrefix() {
return greetingPrefix;
}
public void setGreetingPrefix(String greetingPrefix) {
this.greetingPrefix = greetingPrefix;
}
}
#Configuration
#ConfigurationProperties("brand1")
class Brand1Config extends BaseConfig {
}
#Configuration
#ConfigurationProperties("brand2")
class Brand2Config extends BaseConfig {
}
As you can see, it's fundamental to pass the service to each controller method, write a resolver and inject the right set of dependencies depending on a parameter passed to the request, in this case via header.
Since your property files need to be declared statically anyway, you can just write all your different brand stuff in the same property file, like in a key-value format, that Spring can pick up as a list of configurations.
brandConfigs:
- brand: foo
property: foos
- brand2: bar
porperty: bars
Load all your connection beans to your downstream services on startup and just route to them according to your request param. Imo this seems to be the most straight forward and performant way. If some of these downstreams are used very rarely you can lazy load the beans on-demand, but probably this wouldn't make a sense unless you have thousands of different downstream routes.
Let's say I have this code structure:
public class NotificationService {
public void send(Notification notification) {
// call other services and send the notification
}
}
public class OrderNotification implements Notification {
#Autowired
public TranslationService translationService;
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
So, my goal is to use the NotificationService in this way:
notificationService.send(new OrderNotification(orderNumber));
But I know that code above won't work, because of the translationService won't be resolved.
My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?
I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.
You need to separate your Service from your Entity.
OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.
public class OrderNotification implements Notification {
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return "Order number: " + orderNumber;
}
//Getter & Setters
...
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public void send(Notification notification) {
//I do not know what trans accepts, so I assume it can accept Notification
translationService.trans(notification.getMessage());
}
}
If you really need to combine the entity and service - Then I recommend this approach:
#Service
public class Master{
#Autowired
NotificationService notificationService
public void testMethod(){
Notification notification = notificationService.createOrder("order1");
notificationService.send(notification);
}
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public Notification createOrder(String orderNumber){
return new OrderNotification(orderNumber, translationService);
}
public void send(Notification notification) {
// call other services and send the notification
notification.getMessage();
}
}
public class OrderNotification implements Notification {
private TranslationService translationService;
private String orderNumber;
//I have changed this constructor to accept TranslationService.
public OrderNotification(String orderNumber, TranslationService translationService) {
this.orderNumber = orderNumber;
this.translationService = translationService;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
You have few options available:
Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.
Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.
String orderNumber = "123";
OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
Drop the #Autowired and use plain constructor injection.
public OrderNotification(TranslationService translationService, String orderNumber) {
this.translationService = Objects.requireNonNull(translationService);
this.orderNumber = Objects.requireNonNull(orderNumber);
}
If you only require simple #Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.
I have add some config inside my application.yml file and I want to read it from my Java code.
The added node inside the YAML file looks like this:
myConfig:
projectOne:
mantisID: 501
user: username
password: passwd
projectTwo:
mantisID: 502
user: username
password: passwd
What I want is to get a List of Project objects where
Project.mantisID = 501,
Project.user = "username",
Project.password = "passwd",
etc...
I know spring can read this file with some #Value annotation but how can I use this in order to get what I need?
You can use #ConfigurationProperties annotation to map your configuration to a Bean, then you'll be able to inject your Bean anywhere and fetch those properties.
To do so, first create a class which represents the data structure in your configuration. Then annotate it with #ConfigurationProperties and #Configuration annotations.
#Configuration
#ConfigurationProperties
public class MyConfig {
private final Map<String, Project> myConfig = new HashMap<>();
public Map<String, Project> getMyConfig() {
return myConfig;
}
public static class Project {
private String mantisID;
private String password;
private String user;
// Getters and setters...
}
}
Note that getters and setters are required in the Project class. Also keep in mind that naming of getters and setters is important here.
After you have setup this class, you can inject it anywhere in your project and access its properties.
#Service
public class SomeService {
private final Map<String, MyConfig.Project> projects;
#Autowired
public SomeService(MyConfig config) {
this.projects = config.getMyConfig();
projects.get("projectOne").getMantisID();
projects.get("projectTwo").getPassword();
}
}
You can read more about this here.
Just to finish, I answered myself to my second question.
This is what my service looks like now :
#Service
public class MantisProjectService {
private final Map<String, MantisProjectConfiguration.Project> projects;
private List<MantisProjectConfiguration.Project> mantisProjects = new ArrayList<>();
#Autowired
public MantisProjectService(MantisProjectConfiguration mantisProjectConfiguration)
{
this.projects = mantisProjectConfiguration.getMantisProjectConfiguration();
for (Map.Entry<String, MantisProjectConfiguration.Project> project : projects.entrySet())
{
MantisProjectConfiguration.Project mantisProject = project.getValue();
mantisProject.setName(project.getKey());
mantisProjects.add(mantisProject);
}
}
public List<MantisProjectConfiguration.Project> getMantisProjects()
{
return mantisProjects;
}
}
It returns a List of all the projects. And it is awesome! =)