resilience4j circuit breaker change fallback method return type than actual called method return type - spring-boot

I am trying to learn Spring Boot microservices. Now I am trying to implement circuit breaker with resilience4j if any of my called service is off.
If I set the fallback method return type as like the actual method return type than it works fine but I can't show the information that my service is off. Because it then send the response null in object's fields. But if I change the return type to String on actual method and also in fallback then I will not be able to get the object value as JSON.
Is it possible to return as string something like Branch service is down!.. with my fallback method and if OK then get the object value as JSON from actual called method? My attempts are below:
My controller method:
#GetMapping("/getById/{id}")
#CircuitBreaker(name = "default", fallbackMethod = "employeeFallback")
public ResponseModelEmployee getEmployee(#PathVariable("id") Long id) {
return employeeService.findByEmployeeId(id);
}
My fallback method in controller:
public ResponseModelEmployee employeeFallback(Long id, Exception ex) {
return new ResponseModelEmployee();
}
My service method called from controller:
public ResponseModelEmployee findByEmployeeId(Long id) {
ResponseModelEmployee empDetails = new ResponseModelEmployee();
...
Branch branch = restTemplate.getForObject("http://BRANCH-SERVICE/branch/getById/" +
employee.get().getBranchId(),
Branch.class);
...
return empDetails;
}
My desire method as fallback:
public String employeeFallback(Long id, Exception ex) {
return "Branch Service is down";
}
If I set my desire method for fallback then it gives the following error:
java.lang.NoSuchMethodException: class com.example.employee.VO.ResponseModelEmployee class com.example.employee.controller.EmployeeController.employeeFallback(class java.lang.Long,class java.lang.Throwable) at io.github.resilience4j.fallback.FallbackMethod.create(FallbackMethod.java:92) ~[resilience4j-spring-1.7.0.jar:1.7.0] ....

Resilince4j expects the fallback method to have the same return type as of the actual method.
Documentation says:
It's important to remember that a fallback method should be placed in
the same class and must have the same method signature with just ONE
extra target exception parameter).
If there are multiple fallbackMethod methods, the method that has the
most closest match will be invoked, for example:
If you try to recover from NumberFormatException, the method with
signature String fallback(String parameter, IllegalArgumentException
exception)} will be invoked.
You can define one global fallback method with an exception parameter
only if multiple methods has the same return type and you want to
define the same fallback method for them once and for all.
So, you cannot directly change the return type to a different one.
You can try few suggestions:
Add #CircuitBreaker and fallback to the service method.
Change return type of service method and fallback method to Object.
One more way could be , you can keep the return type as it is but add a String type message object to response model ResponseModelEmployee. Then set message string to it during fallback.
Another solution could be to return ResponseEntity from the from the method where rest call is made and in the fallback method use ResponseEntity<?> as response object.

you can use Object as a return type
in my case for example:
#GetMapping("/getUser/{id}")
#CircuitBreaker(name= something , fallbackMethod = "ContactsServiceDown")
public ResponseEntity<User> getDetailsById(#PathVariable(id)){
//some logic
return new ResponseEntity<User>(user , HttpStatus.OK);
}
public ResponseEntity<Object> ContactsServiceDown(int id , Exception e){
//some logic
return new ResponseEntity<Object>("ContactsServersDown", HttpStatus.Forbidden)
}
or in returnType ResponseEntity<> leave the type Field empty, hopefully it may work!

Related

How to pass data from business method to fallback method while using spring-boot circuit breaker (Hystrix)?

In official https://spring.io/guides/gs/circuit-breaker/ manual there are
business method (readingList)
fallback method (reliable)
#HystrixCommand(fallbackMethod = "reliable")
public String readingList() {
URI uri = URI.create("http://localhost:8090/recommended");
return this.restTemplate.getForObject(uri, String.class);
}
public String reliable() {
return "Cloud Native Java (O'Reilly)";
}
How to pass data from business method to fallback method? Use ThreadLocal, immutable collections, concurrent collections, any ideas/best practice?
Use ThreadLocal?
#HystrixCommand and the corresponding fallbackMethod are normally executed (together) in a separate thread because of the default execution.isolation.strategy which is ExecutionIsolationStrategy.THREAD.
So that means that if you use ThreadLocal to set any variables before #HystrixCommand is executed, they won't be available to the #HystrixCommand because the thread will be different.
If the above is necessary you can use a different isolation strategy - ExecutionIsolationStrategy.SEMAPHORE.
To override the default isolation strategy you can do it on the hystrix command definition (or in properties files):
#HystrixCommand(fallbackMethod = "reliable",
commandProperties = {
#HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
}
)
Passing input parameters data
Methods annotated with #HystrixCommand and the corresponding fallbackMethod need to have the same method signature (plus optional param in the fallback for exceptions thrown), so naturally the fallback method will have access to all input parameters to the #HystrixCommand.
Passing exceptions data
Adding Throwable in the fallback method signature will include the exception produced from the #HystrixCommand:
public String reliable(Throwable t) {
return "Cloud Native Java (O'Reilly)";
}
Passing execution data
It's not practical to expect any execution data to be passed from main method to the fallback method. You don't know when the main method will fail.
A key thing is to try and define better input parameters, which will be shared with the fallback anyway.
For example in the code that you've given the URL can become input parameter, so it will be available to the fallback method as well:
#HystrixCommand(fallbackMethod = "reliable")
public String readingList(String url) {
URI uri = URI.create(url);
return this.restTemplate.getForObject(uri, String.class);
}
public String reliable(String url, Throwable t) {
return "Cloud Native Java (O'Reilly)";
}

Spring Data Rest ava.lang.IllegalArgumentException

I am getting
java.lang.IllegalArgumentException: Cannot get property 'objects' on null object
error when I intentionally test spring data rest api simulating a user passing bad url as in
http://myurl/findByDate?year=&month="
The year and month are Integers , but in this case I am simulating the user not passing any value.
The app is using the:
public interface myRepo extends PagingAndSortingRepository<myClass, Long> {}
interface and there is no explicit controller provided in a controller class. The data rest interface provides the following:
#Query("Select c from myPOJO c where (YEAR(c.myDate) = :year AND MONTH(c.myDate) = :month)")
Page findByDate(#Param("year") int year,
#Param("month") int month,
Pageable pageable);
I can see why the exception is being thrown ... but providing a generic exception handler to handle it does not seem to resolve the issue.
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) {
ErrorResponse error = new ErrorResponse();
error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
error.setMessage("Please contact your administrator");
//return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK);
return new ResponseEntity<ErrorResponse>(error, HttpStatus.METHOD_NOT_ALLOWED);
}
}
Any advise on how to trap this data rest exception would be appreciate it.
java.lang.IllegalArgumentException: Cannot get property 'objects' on null object
Thank you
This is resolved by using the object Integer instead of the primitive Int for the param. This appears to handle the conversion to default value and avoid nulls.
Thanks

Databinding in the controller using spring throws exception

I have a controller with method parameter as model say
public Response create(Customer customer){
}
Customer model :customer model looks like
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME,property = "type")
#JsonSubTypes({#Type(value = Config.class, name = "IPC")})
public class Customer(){
private String type; }
From swagger UI if i send type as IPC its works fine, but any other value than IPC throws an 400 exception while binding.How can i catch this exception inside controller
try to use the #ExceptionHandler annotation
The documentation of Spring 4 (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-exceptionhandler) states that
The HandlerExceptionResolver interface and the
SimpleMappingExceptionResolver implementations allow you to map
Exceptions to specific views declaratively along with some optional
Java logic before forwarding to those views. However, in some cases,
especially when relying on #ResponseBody methods rather than on view
resolution, it may be more convenient to directly set the status of
the response and optionally write error content to the body of the
response.
You can do that with #ExceptionHandler methods. When declared within a
controller such methods apply to exceptions raised by #RequestMapping
methods of that controller (or any of its sub-classes). You can also
declare an #ExceptionHandler method within an #ControllerAdvice class
in which case it handles exceptions from #RequestMapping methods from
many controllers. Below is an example of a controller-local
#ExceptionHandler method:
So, in your controller you can have a method like this
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleArgumentNotValid(MethodArgumentNotValidException e, ModelMap map, HttpServletRequest request) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
//you can get the exception e,
//you can get the request
//handle whatever you want the then return to view
return "your view that you will handle the exception";
}

Customization of Spring ConversionFailedException error HTTP status

I have an enum class:
class enum Type {
LOCAL, REMOTE
}
I have an API that accepts the enum as a GET parameter
#RequestMapping(method = RequestMethod.GET, location="item", params = "type")
public Item[] get(Type type) {
...
When a client calls the API with valid values, like GET /item?type=LOCAL or GET /item?type=REMOTE it works fine. If the client supplies invalid value for type, e.g. GET /item?type=INVALID_TYPE, then Spring generates 500 Internal Server Error. I would like to turn it into 400 Bad Request validation error, potentially adding useful information for the client. I prefer to reuse the built type converter since in works just fine, just want to change a type of error HTTP thrown with minimum changes.
I believe if you add the right exception to #ControllerAdvice, you can customize the response. In this case, I found that MethodArgumentTypeMismatchException was the one in question.
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
public void methodArgumentTypeMismatchException(final HttpServletResponse response) throws IOException {
response.sendError(BAD_REQUEST.value());
}
Why is this happening?
I would consider having a look at the example here about the #ControllerAdvice and/or #ExceptionHandler annotations. The error you're experiencing is occurring because, I believe, Spring tries to construct a Type from the "INVALID_TYPE" string and gets an error when it cannot create a Type from it--because "INVALID_TYPE" is not one of the available values.
What can I do about it?
What you'll want to do is add a string constructor to your enum so it knows, more correctly how to create one of the enum objects, and then check the input to see if its valid. If it is invalid, throw a custom exception. Then, in your #ControllerAdvice, you can customize the HTTP status code of the response.
The exception will then be able to be handled with something like the following:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST) // 409
#ExceptionHandler(MyCustomException.class)
public void handleConflict() {
// handle the exception response, if you need information about the
// request, it should be able to be attached to the custom exception
}
}
The enum would look something like this:
public enum Type{
LOCAL("LOCAL"),
REMOTE("REMOTE");
private String type;
private Type(String type) {
if(type.equals("LOCAL") || type.equals("REMOTE")) {
this.type = type;
} else {
throw new MyCustomException();
}
}
public String getType() {
return url;
}
}

Using MvcUriComponentsBuilder::fromMethodCall with String as the return type

I'd like to use the MvcUriComponentsBuilder::fromMethodCall method to build URLs from my controllers. I normally have a String return type (which returns the view name) and a Model instance as method parameter in my controller methods like:
#Controller
public class MyController {
#RequestMapping("/foo")
public String foo(Model uiModel) {
uiModel.addAttribute("pi", 3.1415);
return "fooView";
}
}
I try to generate a URL e.g. like:
String url = MvcUriComponentsBuilder.fromMethodCall(on(MyController.class).foo(null)).build().toUriString();
This leads to this exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class java.lang.String
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-4.1.4.RELEASE.jar:4.1.4.RELEASE]
This happens because the String return type wants to get proxied, but can't as a final class.
What's a way to overcome this? I'd like to keep the String as a return type and get the Model as input from a parameter in my controller methods because IMHO it's way easier than handling a ModelAndView instance in every controller method.
fromMethodCall uses CGLIB proxy in the process which is why you run into the issue. This article details why.https://github.com/spring-projects/spring-hateoas/issues/155. Try using fromMethodName if you want to maintain the String return types.
MvcUriComponentsBuilder.fromMethodName(MyController.class, "foo", new Object()).build();
Consider changing the signature of the method to return Spring's ModelAndView vs. returning String. For example:
#Controller
public class MyController {
#RequestMapping("/foo")
public ModelAndView foo() {
return new ModelAndView("fooView", "pi", 3.1415);
}
}
With this refactored signature, the corresponding fromMethodCall invocation would look like this:
UriComponents uri = fromMethodCall(on(MyController.class).foo()).build();

Resources