Eric
Eric

Reputation: 494

Capture exception thrown by Hystrix fallback?

I'm designing a service facade and I have a method signature that looks like this:

public Policy getPolicy(long policyId) throws PolicyNotFoundException

If nothing bad happens then a Policy object (simple POJO) is returned. If the requested policy is not found then a checked exception PolicyNotFoundException is thrown (just as a reference - we follow this article when it comes to best practices on exception handling within an application).

The layer above the service facade layer (in this case a Spring MVC RestController) knows how to handle such a PolicyNotFoundException and return an appropriate payload.

I'm trying to incorporate this into a HystrixCommand by doing something like this:

@HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe", ignoreExceptions = { PolicyNotFoundException.class })
public Policy getPolicy(long policyId) throws PolicyNotFoundException {
    LOGGER.info("Getting policy {}", policyId);

    // Simulate some error condition for testing purposes
    throw new RuntimeException("Something happened!");
}

private Policy getPolicySafe(long policyId, Throwable t) throws PolicyNotFoundException {
    LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId, t);
    throw new PolicyNotFoundException(policyId);
}

Basically I want my circuit breaker to simply behave as if the policy wasn't found by the original lookup. The problem I'm having with this though is the exception I throw from the fallback method is getting lost in translation somewhere. The exception I end up seeing in the layer above is the RuntimeException thrown by the command method and not the exception thrown by the fallback method. Is there a way around this? I don't want to change the contract of my original method either nor do I want the layer above this to know anything other than to have to catch PolicyNotFoundException in the case a policy isn't found. Whatever is needed here should be captured within this service facade layer.

Any and all help would be greatly appreciated. Thanks!

Upvotes: 1

Views: 17209

Answers (4)

Alejandro Leone
Alejandro Leone

Reputation: 11

I do this:

@Component
public class HystrixClient {

  @HystrixCommand(ignoreExceptions = {ClientArgumentException.class})
  public POJO getPojo(String id)
        throws ClientNoDataFoundException, ClientArgumentException, ClientGeneralException {

    //call my service and return POJO
  }
}

@Component
public TrueClientUsedForAnotherSerivce {

  @Autowired
  HystrixClient hystrixClient;

 public POJO getPojo(String id)
        throws ClientNoDataFoundException, ClientArgumentException, ClientGeneralException, ClientOpenCircuitException {
    try {           
        POJO result = hystrixClient.getCellular(id);            

        return result;
    }
    catch(HystrixRuntimeException e) {
        LOG.debug("The circuit is open");
        throw new ClientOpenCircuitException("Open circuit");
    }
}   

It only works if @HystrixCommand method is in another class.

Upvotes: 1

Manikandan
Manikandan

Reputation: 83

This is the default behavior of hystrix. "If command has a fallback then only first exception that trigers fallback logic will be propagated to caller"

See the error propagation section here.

Upvotes: 1

dmgcodevil
dmgcodevil

Reputation: 629

While your solution might work for you I've noticed some weirdness in your code (I can't check my assumptions so I would like to ask you to check this).

  1. Try to avoid using checked exceptions in your code because it's awkward to maintain.
  2. Based on your code you will never catch "PolicyNotFoundException" since you're using raiseHystrixExceptions = { HystrixException.RUNTIME_EXCEPTION } which means that you won't to get your custom exception so that HystrixRuntimeException will be propagated. Try to rewrite your code as follows so it should simplify the code and maybe fix some of your problems:

@Service
public class PolicyServiceImpl implements PolicyService {
  @HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe")
  public Policy getPolicy(long policyId) throws PolicyNotFoundException {
    LOGGER.info("Getting policy {}", policyId);
    throw new PolicyNotFoundException(); // throw real PolicyNotFoundException if policy is absent for the given id
  }

  @HystrixCommand(groupKey = "PolicyService")
  private Policy getPolicySafe(long policyId) throws PolicyNotFoundException {
    // Here is we hit our fallback we want to log a warning & simply act as if the policy wasn't found by throwing the same contingency exception as the API does
    LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId);

    throw new PolicyNotFoundException(policyId);
  }
}

Upvotes: 1

Eric
Eric

Reputation: 494

So based on the link @spencergibb gave - I may have found a solution after upgrading to Hystrix 1.5.7. This code works as expected

PolicyRestController.java

@RestController
@RequestMapping("/policies")
public class PoliciesApi {
  private static final Logger LOGGER = LoggerFactory.getLogger(PoliciesApi.class);

  @Autowired
  private PolicyService policyService;

  @RequestMapping(value = "/{policyId}", method = RequestMethod.GET, produces = { MediaTypes.POLICY_JSON_VALUE, MediaTypes.POLICY_XML_VALUE })
  public Policy getPolicy(@PathVariable long policyId) {
    try {
      // This just shown for simplicity. There is more to this method (input validation/etc)
      return this.policyService.getPolicy(policyId);
    }
    catch (PolicyNotFoundException ex) {
      // NotFoundException is a RuntimeException annotated with @ResponseStatus(HttpStatus.NOT_FOUND)
      // So the service returns a 404 to the client
      LOGGER.info("Policy {} wasn't found", ex.getPolicyId(), ex);
      throw new NotFoundException(String.format("Policy %s was not found", ex.getPolicyId()));
    }
  }
}

PolicyService.java

public interface PolicyService {
    @Cacheable("allPolicies")
    public List<Policy> getPolicies();

    @Cacheable("policies")
    public Policy getPolicy(long policyId) throws PolicyNotFoundException;
}

PolicyServiceImpl.java:

@Service
public class PolicyServiceImpl implements PolicyService {
  @HystrixCommand(groupKey = "PolicyService", fallbackMethod = "getPolicySafe", ignoreExceptions = { PolicyNotFoundException.class })
  public Policy getPolicy(long policyId) throws PolicyNotFoundException {
    LOGGER.info("Getting policy {}", policyId);

    // Simulate some error condition for testing purposes
    throw new RuntimeException("Something happened!");
  }

  @HystrixCommand(groupKey = "PolicyService", ignoreExceptions = { PolicyNotFoundException.class }, raiseHystrixExceptions = { HystrixException.RUNTIME_EXCEPTION })
  private Policy getPolicySafe(long policyId) throws PolicyNotFoundException {
    // Here is we hit our fallback we want to log a warning & simply act as if the policy wasn't found by throwing the same contingency exception as the API does
    LOGGER.warn("Falling back to circuit-breaker for getting policy {}", policyId);

    throw new PolicyNotFoundException(policyId);
  }
}

Upvotes: 4

Related Questions