Sign Up

Have an account? Sign In Now

Sign In

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Sign InSign Up

Softans

Softans Logo Softans Logo
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
Home/ Questions/Q 4223
Answered
Seemab Khalid
Seemab Khalid
Asked: December 22, 20222022-12-22T12:03:47+00:00 2022-12-22T12:03:47+00:00

SpringBoot selecting the @Repository based on design pattern and configuration

Small question on Spring Boot, and how to use a design pattern combined with Spring @Value configuration in order to select the appropriate @Repository please.

Setup: A springboot project which does nothing but save a pojo. The “difficulty” is the need to choose where to save the pojo, based on some info from inside the payload request.

I started with a first straightforward version, which looks like this:

   @RestController
public class ControllerVersionOne {

    @Autowired private ElasticRepository elasticRepository;
    @Autowired private MongoDbRepository mongoRepository;
    @Autowired private RedisRepository redisRepository;

    //imagine many more other repositories
//imagine many more other repositories
//imagine many more other repositories

    @PostMapping(path = "/save")
    public String save(@RequestBody MyRequest myRequest) {
        String whereToSave = myRequest.getWhereToSave();
        MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
        if (whereToSave.equals("elastic")) {
            return elasticRepository.save(myPojo).toString();
        } else if (whereToSave.equals("mongo")) {
            return mongoRepository.save(myPojo).toString();
        } else if (whereToSave.equals("redis")) {
            return redisRepository.save(myPojo).toString();
            // imagine many more if 
            // imagine many more if 
            // imagine many more if 

        } else {
            return "unknown destination";
        }
    }

With the appropriate @Configuration and @Repository for each and every databases. I am showing 3 here, but imagine many. The project has a way to inject future @Configuration and @Repository as well (the question is not here actually)

@Configuration
public class ElasticConfiguration extends ElasticsearchConfiguration {

@Repository
public interface ElasticRepository extends CrudRepository<MyPojo, String> {


@Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {

@Repository
public interface MongoDbRepository extends MongoRepository<MyPojo, String> {


@Configuration
public class RedisConfiguration {

@Repository
public interface RedisRepository {

Please note, some of the repositories are not children of CrudRepository. There is no direct ___Repository which can cover everything.

And this first version is working fine. Very happy, meaning I am able to save the pojo to where it should be saved, as I am getting the correct repository bean, using this if else structure. In my opinion, this structure is not very elegant (if it ok if we have different opinion here), especially, not flexible at all (need to hardcode each and every possible repository, again imagine many).

This is why I went to refactor and change to this second version:

@RestController
public class ControllerVersionTwo {

    private ElasticRepository elasticRepository;
    private MongoDbRepository mongoRepository;
    private RedisRepository redisRepository;
    private Map<String, Function<MyPojo, MyPojo>> designPattern;

    @Autowired
    public ControllerVersionTwo(ElasticRepository elasticRepository, MongoDbRepository mongoRepository, RedisRepository redisRepository) {
        this.elasticRepository = elasticRepository;
        this.mongoRepository = mongoRepository;
        this.redisRepository = redisRepository;
// many more repositories
        designPattern = new HashMap<>();
        designPattern.put("elastic", myPojo -> elasticRepository.save(myPojo));
        designPattern.put("mongo", myPojo -> mongoRepository.save(myPojo));
        designPattern.put("redis", myPojo -> redisRepository.save(myPojo));
//many more put
    }

    @PostMapping(path = "/save")
    public String save(@RequestBody MyRequest myRequest) {
        String whereToSave = myRequest.getWhereToSave();
        MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
        return designPattern.get(whereToSave).apply(myPojo).toString();
    }

As you can see, I am leveraging a design pattern refactoring the if-else into a hashmap.

This post is not about if-else vs hashmap by the way.

Working fine, but please note, the map is a Map<String, Function<MyPojo, MyPojo>>, as I cannot construct a map of Map<String, @Repository>.

With this second version, the if-else is being refactored, but again, we need to hardcode the hashmap.

This is why I am having the idea to build a third version, where I can configure the map itself, via a spring boot property @Value for Map:

Here is what I tried:

@RestController
public class ControllerVersionThree {

    @Value("#{${configuration.design.pattern.map}}")
    Map<String, String> configurationDesignPatternMap;

    private Map<String, Function<MyPojo, MyPojo>> designPatternStrategy;

    public ControllerVersionThree() {
        convertConfigurationDesignPatternMapToDesignPatternStrategy(configurationDesignPatternMap, designPatternStrategy);
    }

    private void convertConfigurationDesignPatternMapToDesignPatternStrategy(Map<String, String> configurationDesignPatternMap, Map<String, Function<MyPojo, MyPojo>> designPatternStrategy) {
        // convert configurationDesignPatternMap
        // {elastic:ElasticRepository, mongo:MongoDbRepository , redis:RedisRepository , ...}
        // to a map where I can directly get the appropriate repository based on the key
    }

    @PostMapping(path = "/save")
    public String save(@RequestBody MyRequest myRequest) {
        String whereToSave = myRequest.getWhereToSave();
        MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
        return designPatternStrategy.get(whereToSave).apply(myPojo).toString();
    } 

And I would configure in the property file:

configuration.design.pattern.map={elastic:ElasticRepository, mongo:MongoDbRepository , saveToRedis:RedisRepositry, redis:RedisRepository , ...}

And tomorrow, I would be able to configure add or remove the future repository target.

configuration.design.pattern.map={elastic:ElasticRepository, anotherElasticKeyForSameElasticRepository, redis:RedisRepository , postgre:PostGreRepository}

Unfortunately, I am stuck.

What is the correct code in order to leverage a configurable property for mapping a key with it’s “which @Repository to use” please?

Thank you for your help.

springboot selecting the @repository based on design pattern and configuration
  • 1
  • 1 1 Answer
  • 13 Views
  • 0 Followers
  • 0
Answer
Share
  • Facebook
  • Report

1 Answer

  • Voted
  • Oldest
  • Recent
  1. Best Answer
    Ghulam Nabi
    2022-12-22T12:04:50+00:00Added an answer on December 22, 2022 at 12:04 pm

    Short answer:

    • create a shared interface
    • create multiple sub-class of this interface (one per storage) using different spring component names
    • use Spring context to retrieve the right bean by name (instead of creating a custom factory)

    Now adding a new storage is only adding a new Repository classes with a name

    Explanation: As mentioned in the other answer you first need to define a common interface as you can’t use the CrudRepository.save(...). In my example I reuse the same signature as the save method to avoid re-implementing it in the sub-classes of CrudRepository.

    public interface MyInterface<T> {
        <S extends T> S save(S entity);
    }
    

    Redis Repository:

    @Repository("redis") // Here is the name of the redis repo
    public class RedisRepository implements MyInterface<MyPojo>  {
        @Override
        public <S extends MyPojo> S save(S entity) {
            entity.setValue(entity.getValue() + " saved by redis");
            return entity;
        }
    }
    

    For the other CrudRepository no need to provide an implementation:

    @Repository("elastic") // Here is the name of the elastic repo
    public interface ElasticRepository  extends CrudRepository<MyPojo, String>, MyInterface<MyPojo> {
    }
    

    Now create the version three of your repository with the injection of SpringContext:

    @RestController
    public class ControllerVersionThree {
    
        private final ApplicationContext context;
    
        public ControllerVersionThree(ApplicationContext context) {
            this.context = context;
        }
    
        @PostMapping(path = "/save")
        public String save(@RequestBody MyRequest myRequest) {
            String whereToSave = myRequest.getWhereToSave();
            MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
            // Here, you should check the value of whereToSave to avoid some 'spring injection vulnerabilities'  
            MyInterface<MyPojo> repo = context.getBean(whereToSave, MyInterface.class);
            return repo.save(myPojo).toString();
        }
    }
    

    You can check that this is working with a test:

    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.boot.test.web.server.LocalServerPort;
    import org.springframework.http.HttpEntity;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    class ControllerVersionThreeTest {
        @LocalServerPort
        private int port;
    
        @Autowired
        private TestRestTemplate restTemplate;
    
        Dhaval Shah
        void testSaveByRedis() {
            // Given: here 'redis' is the name of th spring beans
            HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("redis", "aValue"));
    
            // When
            String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
    
            // Then
            assertEquals("MyPojo{value='aValue saved by redis'}", response);
        }
    
    }
    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question
  • Popular
  • Answers
  • Ghulam Nabi

    Why are the British confused about us calling bread rolls ...

    • 5 Answers
  • Ghulam Nabi

    Is this statement, “i see him last night” can be ...

    • 4 Answers
  • Alex

    application has failed to start because no appropriate graphics hardware ...

    • 4 Answers
  • Ghulam Nabi
    Ghulam Nabi added an answer It seems that the issue you are facing is a… January 27, 2023 at 1:37 pm
  • Ghulam Nabi
    Ghulam Nabi added an answer The error "E/libEGL: validate_display:99 error 3008 (EGL_BAD_DISPLAY)" is caused by… January 27, 2023 at 1:35 pm
  • Ghulam Nabi
    Ghulam Nabi added an answer The Chrome browser uses both memory cache and disk cache… January 27, 2023 at 1:32 pm

Trending Tags

android c++ cypress flutter java javascript python selenium testng webdriver

Top Members

Robert

Robert

  • 3 Questions
  • 1k Points
Luci

Luci

  • 5 Questions
  • 1k Points
Kevin O Brien

Kevin O Brien

  • 2 Questions
  • 1k Points

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Softans

Softans is a social questions & Answers Engine which will help you establish your community and connect with other people.

About Us

  • Blog
  • Jobs
  • About Us
  • Meet The Team
  • Contact Us

Legal Stuff

Help

Follow

© 2021 Softans. All Rights Reserved
With Love by Softans.

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.