spring-bootrest-clientcircuit-breakervirtual-threadsjava-21

Seeking Enhancements for RestClient Configuration


I'm using RestClient for upstream communication and would like to add some enhancements to make it more robust. Here's my current setup:

    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(300);
    factory.setReadTimeout(300);

    RestClient restClient = RestClient.builder()
                .requestFactory(factory)
                .baseUrl("http://...:8080/..")
                .build();

    Map<String, Object> response = restClient.get()
                .uri(backend)
                .retrieve()
                .body(Map.class);

Regarding Environment:

  1. JDK21
  2. org.springframework.boot:3.2.0-M3
  3. spring.threads.virtual.enabled=true

I'd appreciate suggestions on:

  1. Integrating a circuit breaker with RestClient.
  2. Implementing a retry mechanism.
  3. Configuring the client to use a proxy gateway.

Has anyone done this before or can point me to relevant resources?


Solution

  • Here are the working solution I have.

    /**
     * A service class responsible for processing backend requests with 
     * retry and circuit breaker mechanisms.
     */
    @Service
    public class Processor {
    
        private final RestClient restClient;
        private final ExecutorService executor;
        private final RetryTemplate retryTemplate;
        private static final String RESULT = "result";
        private static CircuitConfig circuit;
    
        /**
         * Constructor for the Processor class.
         *
         * @param circuit The circuit breaker configuration.
         */
        @Autowired
        public Processor(CircuitConfig circuit) {
            this.circuit = circuit;
            
            // Setting up client request factory with timeouts
            SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
            simpleClientHttpRequestFactory.setConnectTimeout(300);
            simpleClientHttpRequestFactory.setReadTimeout(300);
            
            this.restClient = RestClient.builder()
                                        .requestFactory(simpleClientHttpRequestFactory)
                                        .baseUrl("http://...:8080/../")
                                        .build();
    
            this.executor = Executors.newVirtualThreadPerTaskExecutor();
            this.retryTemplate = createRetryTemplate();
            System.out.println("CompletableFuture...");
        }
    
        /**
         * Creates a retry template with exponential back-off policy.
         *
         * @return A configured RetryTemplate instance.
         */
        private RetryTemplate createRetryTemplate() {
            RetryTemplate retryTemplate = new RetryTemplate();
            
            // Setting exponential back-off policy
            ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
            backOffPolicy.setInitialInterval(100);
            backOffPolicy.setMultiplier(2.0);
            backOffPolicy.setMaxInterval(1000);
            retryTemplate.setBackOffPolicy(backOffPolicy);
    
            // Setting simple retry policy with max attempts
            SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
            retryPolicy.setMaxAttempts(3);
            retryTemplate.setRetryPolicy(retryPolicy);
    
            return retryTemplate;
        }
    
        /**
         * Retrieves backend response with retry mechanism.
         *
         * @param backend The backend URI.
         * @return A map representing the backend response.
         */
        public Map getBackendResp(String backend) {
            return retryTemplate.execute(retryContext ->
                    restClient
                    .get()
                    .uri(backend)
                    .retrieve()
                    .body(Map.class));
        }
    
        /**
         * Wraps the backend request within a CompletableFuture for asynchronous processing.
         *
         * @param backend The backend URI.
         * @return A CompletableFuture containing a map representing the backend response.
         */
        private CompletableFuture<Map> getFuture(String backend) {
            return CompletableFuture.supplyAsync(() -> circuit.circuitBreaker.executeSupplier(() -> getBackendResp(backend)));
        }
    }
    

    I wanted to understand if everything operates under the virtual thread paradigm, but this aspect remains ambiguous to me.