Reputation: 494
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
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
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
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).
@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
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