jax rs - Spring Cloud - HystrixCommand - How to properly enable with shared libraries


Keywords:spring  boot 


Question: 

Using Springboot 1.5.x, Spring Cloud, and JAX-RS:

I could use a second pair of eyes since it is not clear to me whether the Spring configured, Javanica HystrixCommand works for all use cases or whether I may have an error in my code. Below is an approximation of what I'm doing, the code below will not actually compile.

From below WebService lives in a library with separate package path to the main application(s). Meanwhile MyWebService lives in the application that is in the same context path as the Springboot application. Also MyWebService is functional, no issues there. This just has to do with the visibility of HystrixCommand annotation in regards to Springboot based configuration.

At runtime, what I notice is that when a code like the one below runs, I do see "commandKey=A" in my response. This one I did not quite expect since it's still running while the data is obtained. And since we log the HystrixRequestLog, I also see this command key in my logs.

But all the other Command keys are not visible at all, regardless of where I place them in the file. If I remove CommandKey-A then no commands are visible whatsoever.

Thoughts?

// Example WebService that we use as a shared component for performing a backend call that is the same across different resources


@RequiredArgsConstructor
@Accessors(fluent = true)
@Setter
public abstract class WebService {

    private final @Nonnull Supplier<X> backendFactory;
    
    @Setter(AccessLevel.PACKAGE)
    private @Nonnull Supplier<BackendComponent> backendComponentSupplier = () -> new BackendComponent();
    
    
    @GET
    @Produces("application/json")
    @HystrixCommand(commandKey="A")
    public Response mainCall() {

        Object obj = new Object();
        
        try {
            otherCommandMethod();
        } catch (Exception commandException) {
            // do nothing (for this example)
        }
        
        // get the hystrix request information so that we can determine what was executed
        
        Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = hystrixExecutedCommands();

        // set the hystrix data, viewable in the response
        obj.setData("hystrix", executedCommands.orElse(Collections.emptyList()));

        if(hasError(obj)) {
            return Response.serverError()
                        .entity(obj)
                        .build();
        }
        return Response.ok()
                .entity(healthObject)
                .build();
        
    }

    @HystrixCommand(commandKey="B")
    private void otherCommandMethod() {
        backendComponentSupplier
                .get()
                .observe()
                .toBlocking()
                .subscribe();
    }

    Optional<Collection<HystrixInvokableInfo<?>>> hystrixExecutedCommands() {
        Optional<HystrixRequestLog> hystrixRequest = Optional
                .ofNullable(HystrixRequestLog.getCurrentRequest());

        // get the hystrix executed commands
        Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = Optional.empty();
        if (hystrixRequest.isPresent()) {
            executedCommands = Optional.of(hystrixRequest.get()
                    .getAllExecutedCommands());
        }
        return executedCommands;
    }
    
    
    @Setter
    @RequiredArgsConstructor
    public class BackendComponent implements ObservableCommand<Void> {
    
        @Override
        @HystrixCommand(commandKey="Y")
        public Observable<Void> observe() {
             // make some backend call
            return backendFactory.get()
                    .observe();
        }
        
    }

}


// then later this component gets configured in the specific applications with sample configuraiton that looks like this:

@SuppressWarnings({ "unchecked", "rawtypes" })
@Path("resource/somepath")
@Component
public class MyWebService extends WebService {

    @Inject
    public MyWebService(Supplier<X> backendSupplier) {
        super((Supplier)backendSupplier);
    }

}

1 Answer: 

There is an issue with mainCall() calling otherCommandMethod(). Methods with @HystrixCommand can not be called from within the same class.

As discussed in the answers to this question this is a limitation of Spring's AOP.