Hystrix fallback method best practice - java

Ok so I couldn't find any helpful materials on this topic, a big chunk of articles I found had one method that was annotated with #HystrixCommand and had defined a fallback method.
The other solution I found was using #DefaultProperties(defaultFallback = "fallbackMethod") but the problem with this is that the methods need to have compatible return types.
Unfortunately for me in my service I have many methods with completely different signatures and also I need to get hold of the throwable (in docs it is mentioned that you cannot have any parameters for a default fallback method). The methods look something like this:
#Service
#RequiredArgsConstructor
public class MyService {
private final FeignClient feignClient;
#Override
public String methodA(final CustomObjectA o, final String entity) {
...
}
#Override
public String methodB(final String collection, final Map<String, Object> requestBody) {
...
}
#Override
public String methodC(final String collection, final String id, final Map<String, Object> requestBody) {
...
}
}
And ofc I have more than 3 methods def in the service...
The thing I really want to avoid is making 20 hystrix default fallback methods.
Is there a way where I could def a standard fallback for all methods, no matter what the signatures they have, or am I stuck with defining a fallback method for every single method?
Thanks in advance!!

You will have to implement a fall back for each method.
However using the FallbackFactory might make this easier and allow each method to call one reusable method.
Maybe you don't really want hystrix fallbacks if they are the same for each method. All try catch might solve the same problem.

Let me share the code snippet used in my project.
To call an api like http://www.baidu.com/xxx, you have below steps to follow.
1.Api Definition (fallback = WebServiceApiFallback.class)
#Component
#FeignClient(value = "webServiceApi", configuration = FeignConfiguration.class, fallback = WebServiceApiFallback.class)
public interface WebServiceApi {
#Headers(value = {"Content-Type: application/json", "Accept-Encoding: gzip,deflate"})
#GetMapping(value = "/xxx")
BaseResponse<YourResponse> xxx(YourRequest request);
2.Fallback Definition
#Component
public class WebServiceApiFallback implements WebServiceApi {
#Override
public BaseResponse<YourResponse> xxx(YourRequest request) {
// Your Fallback Code here, when api request failed.
}
3.api host configuration, maybe application.properties...
webServiceApi.ribbon.listOfServers=http://www.baidu.com
4.use it
#Autowired
private WebServiceApi webServiceApi;
For any api, you can just define you request and response, and feign will do the request、 encode、and decode.
[Ref] https://github.com/spring-cloud/spring-cloud-netflix/issues/762

Related

How to test with Postman a controller's method that has one or more objects parameters (and not simple params) in Spring?

I am a newbie in Spring development. I need to create a simple application, a controller that has a method that takes as parameter an object of a custom designed entity class into the project. The prototype looks like this:
#RestController
public class JobsController {
#PostMapping("/search")
public ResponseEntity<?> search() {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
Can someone who is more advanced into this staff with Postman testing tell me how to do that , how to test a controller method which takes parameters?
You can use postman to submit parameters in JSON format after adding # requestbody annotation on the method, or submit parameters directly in form without annotation
You can use this example. Is very simple exemple.
#RestController
#RequestMapping("/root")
public class RootController {
private final RootService service;
public RootController(final RootService service) {
this.service = service;
}
#PostMapping("/exemple")
public void createRoot(#RequestBody final RootDto dto) {
service.createRoot(dto);
}
}
Then you can send request to POST host/root/exemple with your JSON.
More exampls you can find here: https://www.baeldung.com/spring-request-response-body
It seems you are missing an honest search on google about the subject.
You can make use of #RequestBody annotation to accept method arguments.
Check these page for examples --
#RequestBody and #ResponseBody annotations in Spring
https://stackabuse.com/get-http-post-body-in-spring/
https://www.twilio.com/blog/create-rest-apis-java-spring-boot
These set of playlist on youtube are very good starter course for SpringBoot -
https://www.youtube.com/c/JavaBrainsChannel/playlists
Postman Tutorial--
https://www.youtube.com/watch?v=VywxIQ2ZXw4
To get data from api is preferred to use GET method :
#RestController
public class JobsController {
#GetMapping("/search")
public ResponseEntity<?> search(#RequestParam("id") String id,#RequestParam("desc") String desc) {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
you call this api with post man this way :
#PostMapping used usually to save new data (example : create job )
Take look on rest resource naming guide

How to wrap an annotation and conditionally applies it to a method

Say I have an annotation (#RequiresAccount) introduced in another library and I'm using it in my project, is there a way to conditionally apply it to a method, e.g. apply it when the customer is from website A and not apply when customer is from website B?
I've taken a look and the only possibility I've found was, creating a wrapper-Annotation:
#Aspect
#Component
public class RequiresAccountWrapperAspect {
#Autowired
private HttpServletRequest request;
private RequiresAccountAspect requiresAccountAspect = new RequiresAccountAspect();
#Around("#annotation(com.example.demo.components.RequiresAccountWrapper)")
public Object checkIfRequiresAccount(ProceedingJoinPoint joinPoint) throws Throwable {
String requestURL = request.getRequestURL().toString();
if (requestURL.startsWith("http://localhost")) {
requiresAccountAspect.checkAccount(joinPoint);
}
return joinPoint.proceed();
}
}
So everywhere you've used your RequiresAccount annotation, you can use this wrapper instead. For example:
#GetMapping("/test")
#RequiresAccountWrapper
public String h() {
return "test";
}
As you can see I'm creating a new instance of the aspect. I don't know if you have access to the Aspect-class itself but if you have you can then call the method in it and pass the joinPoint. To find the URL from the request you can inject the HttpServletRequest.

Is there a way to access a PathVariable in a controller level #PreAuthorize annotation?

is there a way to access the httpRequest within a controller level #PreAuthorize annotation to obtain a PathVariable and use it for the expression inside the #PreAuthorize annotation?
I want to be able to do the following, where #somePathVariable would result in the actual value passed for the PathVariable:
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#somePathVariable)")
public class SomeController { ... }
It also would be sufficient if i could access the HttpServletRequest and get the PathVariable manually.
Please note that this expression is at the controller level before answering. I'd appreciate any help!
So as #pvpkiran already commented. It's probably not possible to get the param the way i want. However his workaround with using a bean to access the PathVariables manually seems to work just fine.
#Component
#RequiredArgsConstructor
public class RequestHelper {
private final HttpServletRequest httpServletRequest;
/* https://stackoverflow.com/questions/12249721/spring-mvc-3-how-to-get-path-variable-in-an-interceptor/23468496#23468496 */
public Object getPathVariableByName(String name) {
final Map pathVariables = (Map) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return pathVariables.get(name);
}
}
and
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#requestHelper.getPathVariableByName('somePathVariable'))")
public class SomeController { ... }
did the job. It's not perfect but it works. The other (prob. better) option is to use #PreAuthorize on method level.
Thanks for your help!

How to map a request in Spring MVC to a Java Object

I'm new to Spring, and since Spring provides many ways to map an HTTP request to Java objects, I'm hoping someone could advice me how to resolve this:
I have a client that sends a request having
ContentType: application/x-www-form-urlencoded
Some of the request parmeters have names such as
"form_data[orderStatus]", "form_data[orderNumber]", etc'
I have no control over this client!
I have a java class (#Component) called MyOrder which looks as follows:
#component
#Scope("prototpe")
public class MyOrder {
private String orderStatus;
private String orderNumber;
//etc'
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
//public setter for all other properties, as the above
}
What is the simplest way to create an instance of MyOrder
populated with all values of all "form_data[]", so that I can have a controller method having a signature that includes a MyOrder parameter, such as:
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
#ModelAttribute("myOrder") MyOrder anOrder) {
//... impl'n here
}
The best solution I could think of was to use a Web Filter which would flaten request params names such as "form_data[attrib1]" to "attrib1", and then Spring would do all the work of populating the MyOrder instance.
One disadvantage of this is that the request may have both "form_data[attrib1]" and "attrib1" parameters. For example:
form_data[orderStatus]=ok
orderStatus=fail
In this case i want MyOrder.orderStatus to have the value "ok".
Any good way of utilizing Spring create MyOrder from the request?
As an alternative, that does not use the class MyOrder, is there a way to have Spring map all the form_data[] parameters and their values to a map, so that i can have the controller method below?
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
<some annotation> #Map<String,String> formFieldsOfAnOrder) {
//... impl'n here
orderStatus = formFieldsOfAnOrder.get("orderStatus");
//or at least:
orderStatus = formFieldsOfAnOrder.get("form_data[orderStatus]");
}

Jersey: forbid unspecified parameters

I'm developing REST API with Jersey as JAX-RS implementation.
In every resource I explicitly define expected parameters:
#GET
#Path("/someData")
public Response getSomeData(
#QueryParam("id") final Long id,
#QueryParam("name") final String name) {
...
}
There are a number of fixed parameters, which are common for all resources (e.g. "locale").
Is there any way (I'm ok with introducing Jersey-specific dependencies) I can forbid any parameters that belong neither to method parameters nor to the common parameters?
So for example if user invokes
/api/resource/someData?id=10&locale=en - he gets the data, but if he invokes
/api/resource/someData?id=10&locale=en&fakeParam=AAA - status 400 is returned, with content stating that fakeParam is unknown parameter.
Currently second request is processed the same way as the first one, and fakeParam is simply ignored.
I think described validation will help users of my API to spot bugs earlier.
I don't know of any way to do this with JAX-RS but you could easily roll your own solution. This is a bit cumbersome but you could do something like:
#Path("/api")
public class Service {
#Context
UriInfo uriInfo;
ImmutableSet<String> commonParams = ImmutableSet.of("locale");
#GET
#Path("validate")
#Produces(MediaType.APPLICATION_JSON)
public String validate(#QueryParam("foo") String param) {
Set<String> validParams = newHashSet(commonParams);
class Local {};
for (Annotation[] annotations: Local.class.getEnclosingMethod().getParameterAnnotations()) {
for (Annotation annotation: annotations) {
if (annotation instanceof QueryParam) {
validParams.add(((QueryParam)annotation).value());
}
}
}
if (!difference(uriInfo.getQueryParameters().keySet(), validParams).isEmpty()) {
//throw an unknown parameter exception
}
return "hello";
}
And if you're using Guice or some other AOP tool with Jersey you could probably put this into an aspect s.t. you wouldn't have to add boilerplate to every method you want to validate.

Categories