Search code examples
configurationhystrixopenfeign

How do you override the Hystrix configuration for OpenFeign?


How do you override the Hystrix default configuration for OpenFeign? Most of the documentation out there is for SpringBoot + OpenFeign, which has its own Spring-specific configuration override system.

Ideally it would be possible to configure the Hystrix core size for the client and configure and timeouts on a per endpoint basis.


Solution

  • Hystrix OpenFeign has a setterFactory() method on the builder that allows you to pass in a SetterFactory lambda function that is executed when setting up each target endpoint:

    final SetterFactory hystrixConfigurationFactory = (target, method) -> {
        final String groupKey = target.name();
        final String commandKey = method.getAnnotation(RequestLine.class).value();
    
        // Configure default thread pool properties
        final HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties = HystrixThreadPoolProperties.Setter()
            .withCoreSize(50)
            .withMaximumSize(200)
            .withAllowMaximumSizeToDivergeFromCoreSize(true);
    
        return HystrixCommand.Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
            .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
            .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties);;
    };
    
    final MyTargetClient myTargetClient = HystrixFeign.builder()
        .setterFactory(hystrixConfigurationFactory)
        .client(new OkHttpClient())
        .encoder(new JacksonEncoder(objectMapper))
        .decoder(new JacksonDecoder(objectMapper))
        .target(new Target.HardCodedTarget<>(MyTargetClient.class, "customclientname", baseUrl))
    

    The above example uses boilerplate from the OpenFeign documentation to properly name Hystrix keys based on the target endpoint function. It then goes further by also configuring the default thread pool property core size and maximum core size as a default for all of the target functions.

    However, since this factory is called for each target endpoint, we can actually override the Hystrix configuration on a per endpoint basis. A use good case for this is Hystrix timeouts: sometimes there are endpoints that take longer than others and we need to account for that.

    The easiest way would be to first create an annotation and place it on the target endpoints that need to be overridden:

    /**
     * Override Hystrix configuration for Feign targets.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface HystrixOverride {
        int DEFAULT_EXECUTION_TIMEOUT = 2_000;
    
        /**
         * Execution timeout in milliseconds.
         */
        int executionTimeout() default DEFAULT_EXECUTION_TIMEOUT;
    }
    
    interface MyTargetClient {
        @HystrixOverride(executionTimeout = 10_000)
        @RequestLine("GET /rest/{storeCode}/V1/products")
        Products searchProducts(@Param("storeCode") String storeCode, @QueryMap Map<String, Object> queryMap);
    
        @RequestLine("GET /rest/{storeCode}/V1/products/{sku}")
        Product getProduct(@Param("storeCode") String storeCode, @Param("sku") String sku);
    }
    

    In the above example, the search API might take a little longer to load so we have an override for that.

    Just putting the override annotation on the target endpoint function is not enough though. We need to go back to our factory and update it to use the data in the annotations:

    final SetterFactory hystrixConfigurationFactory = (target, method) -> {
        final String groupKey = target.name();
        final String commandKey = method.getAnnotation(RequestLine.class).value();
    
        // Configure per-function Hystrix configuration by referencing annotations
        final HystrixCommandProperties.Setter hystrixCommandProperties = HystrixCommandProperties.Setter();
        final HystrixOverride hystrixOverride = method.getAnnotation(HystrixOverride.class);
    
        final int executionTimeout = (hystrixOverride == null)
            ? HystrixOverride.DEFAULT_EXECUTION_TIMEOUT
            : hystrixOverride.executionTimeout();
        hystrixCommandProperties.withExecutionTimeoutInMilliseconds(executionTimeout);
    
        // Configure default thread pool properties
        final HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties = HystrixThreadPoolProperties.Setter()
            .withCoreSize(50)
            .withMaximumSize(200)
            .withAllowMaximumSizeToDivergeFromCoreSize(true);
    
        return HystrixCommand.Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
            .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
            .andCommandPropertiesDefaults(hystrixCommandProperties)
            .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties);;
    };
    

    The above checks that an override annotation exists and then uses the data in that annotation to configure the execution timeout for that target endpoint. If the override is not present, the default for the HystrixOverride endpoint will be used instead. The resulting hystrixCommandProperties variable is then plugged in to the overall HystrixCommand.Setter at the end.