Search code examples
springresilience4jspring-cloud-config-server

How to decorate service method in external dependency with Resilience4j circuit breaker (specifically on spring cloud config server)?


How can I decorate a service method in an external library with Resilience4j circuit breaker? Specifically, I want to decorate a method in spring cloud config server (the server itself, not client code): org.springframework.cloud.config.server.environment.EnvironmentController.labelled(...). It takes multiple parameters. I can't annotate it because it is in a 3rd-party library.

EnvironmentController class is a Spring @RestController.

I see methods in CircuitBreaker for decorating Callables, Functions, etc., but none that seem to apply to this. (I'm completely new to Resilience4j, so I'm hoping I'm missing a simple solution.)


Solution

  • A common approach (not only to Resilience4j, but to Spring in general) would be to use a BeanPostProcessor (you can see a non-related example here).

    Then in the beanPostProcessor, you can get a handle of your EnvironmentController and wrap around its implementation/method with your Resilience4j CicrcuitBreaking logic.

    Basically the approach would be something like:

    • Use a BeanPostProcessor in your @Configuration to get a handle of the fully wired EnvironmentController
    • Wrap your own CircuitBreaker implementation with Resilience4j around the EnvirommentController's methods that you are interested in
    • Profit

    If that doesn't fully clear the picture, I can add some sample code to get you started, just let me know. Keep in mind that's probably just one of the many ways this can be approached.

    Edit:

    Some code (not sure if it works, haven't tested, spring-boot is notoriously annoying in terms of using a shit-ton of aop for the MVC mapping autoconfig, so you might have to play around with aspects or proxy config instead), might look like (keep in mind I'd use lombok to avoid all the boilerplate, for posterity):

    @Configuration
    @Slf4j
    public class MyConfig {
    
        // or however the heck you get your breaker config
        private final CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().build();
        private final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
        private final CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("suchBreakerMuchWow");
    
        @Bean
        public CircuitBreakerAwarePostProcessor circuitBreakerAwarePostProcessor() {
            return new CircuitBreakerAwarePostProcessor();
        }
    
        public class CircuitBreakerAwarePostProcessor implements BeanPostProcessor {
    
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof EnvironmentController) {
                    return new CircuitBreakerFriendlyEnvironmentController((EnvironmentController) bean);
                }
                return bean;
            }
        }
    
        private interface Exclude {
            Environment labelled (String name, String profiles, String label);
        }
    
        @RequiredArgsConstructor
        private class CircuitBreakerFriendlyEnvironmentController implements Exclude {
    
            @Delegate(types = EnvironmentController.class, excludes = Exclude.class)
            @NonNull private final EnvironmentController environmentController;
    
            @Override
            public Environment labelled(String name, String profiles, String label) {
                return circuitBreaker.executeSupplier(() -> {
                    log.info("such wow");
                    return environmentController.labelled(name, profiles, label);
                });
            }
        }
    }