Java Feign Fallback class - java

I am using SpringBoot version 1.5.9.
I can’t understand why my Fallback class doesn’t work out.
Maybe I'm doing something wrong?
My Feign client:
#FeignClient(
name = "prices",
url = "${prices.url}",
configuration = MyFeignConfig.class,
fallbackFactory = FallbackClass.class
)
public interface PricesFeignClient {
#GetMapping("/{userId}")
PriceModel get(
#PathVariable("userId") String userId
);
}
Here is the fallback class:
#Component
public class FallbackClass implements FallbackFactory<PricesFeignClient> {
#Override
public PricesFeignClient create(Throwable cause) {
return new PricesFeignClient() {
#Override
public PriceModel get(String userId) {
System.out.println("LALALA");
return null;
}
};
}
}
In theory, my fallback method should work out if my Feign client returns an error.
Here in the Feign client in the files in prices.url I specified the wrong URL (simulated the situation that my remote service to which I am making a call is unavailable). Knowing my Feign client should return with an error and the Fallback class should be called in which in the console I should receive the message: "LALALA".
This message is not in the console: my Fallback class is not being called. Instead, I get an error stating that the requested resource was not found.
Please tell me what could be the problem? Can I make a mistake somewhere?
The thing is that now I'm trying to get my Fallback class to work. And then I want to call another Fagnet class in the Fallback class with a different URL so that it works out if my main service is unavailable.
Tell me, please. thanks

I had to add to dependencies this for it to work (also don't forget to insert feign.hystrix.enabled: true as was said in the comments)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>

Related

Angular 12 front end returning encoded get request

I'm using mysql for my db, spring for my backend and angular for my frontend. my frontend is throwing this weird bug when its routed proper: click here to see it
as you can see, the path at the end is %7Bid%7D (looks like {id} from the backend)
the http error code is always one of 3: 400,400 or 500
the backend looks okay and I've only really ever gotten this error code:
2022-02-04 15:30:31.465 WARN 15200 --- [nio-8081-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; nested exception is java.lang.NumberFormatException: For input string: "{id}"]
here is the controller in question(a get request):
#CrossOrigin
#RestController
#RequestMapping(path = "/api/patient")
public class PatientPortalController {
#Autowired
private PatientPortalRepo patientPortalRepo;
#PostMapping("/patientportal")
public PatientPortal createPatientPortal(#RequestBody PatientPortal patientportal) {
return patientPortalRepo.save(patientportal);
}
#GetMapping("/patientportal/{id}")
public ResponseEntity<PatientPortal> getpatientPortal(#PathVariable Long id){
PatientPortal patientportal = patientPortalRepo.findByPatientPortalId(id);
if(patientportal.getId()>0 && patientportal!=null)
return new ResponseEntity<PatientPortal>(patientportal, HttpStatus.OK);
return new ResponseEntity<PatientPortal>(patientportal, HttpStatus.BAD_REQUEST);
}}
Some things worth of note that I've tried with the backend
Tried changing response entity to of type long and returning id, tried refactoring the controller numerous times, tried changing decorators/paths around, 20x checked the types are correct, checked if any types other than the id are throwing it, checked if I had any security implemented that was denying access, checked if adding a onetoone would get it to pop up on the front end. It works fine on the backend(returns a list of what I'd assume is patientportal object) but I am either routing incorrectly, there is some security I'm missing, there is some type error, or there is some logic errors. I think however the issue lies in the front end.
here's the code where I call the front end method hard coded a value to test:
this.patientloginservice.loginPatient(this.patient).subscribe(data=>(this.route.navigate(['api/patient/patientportal/1'])),error=>console.log('error'));
and here is where that code is serviced:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http'
import { Observable } from 'rxjs'
import { PatientPortal } from './patientportal';
#Injectable({
providedIn: 'root'
})
export class PatientService {
private baseURL = "http://localhost:8081/api/patient/patientportal";
constructor(private httpClient: HttpClient) { }
getPatientPortalList(): Observable<PatientPortal[]> {
return this.httpClient.get<PatientPortal[]>(`${this.baseURL}`);
}
createPatientPortal(patientportal: PatientPortal): Observable<Object>{
return this.httpClient.post<Object>(`${this.baseURL}`, patientportal);
}
getPatientPortalById(id: number): Observable<PatientPortal>{
return this.httpClient.get<PatientPortal>(`${this.baseURL}/{id}`);
}
updatePatientPortal(id: number, patientportal: PatientPortal): Observable<Object>{
return this.httpClient.put(`${this.baseURL}/{id}`, patientportal);
}
deletePatientPortal(id: number): Observable<Object>{
return this.httpClient.delete(`${this.baseURL}/{id}`);
}
}
any help will be much appreciated, thank you. again like I said the route routes correctly as far as I can tell, but the rendered table does not fill data and it throws that error. I am using a login that is to redirect/route to a patient's details.
You're using Template literals incorrectly.
Instead of just {id} it should be ${id} just like what you did with ${this.baseUrl}
Hope that solves your issue.

Spring IntegrationFlow is failing due to incorrect handle method

Hi I am trying to add Spring IntegrationFlow but dont know what is the error for following scenario.
My IntegrationConfig is as below
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class IntegrationConfig {
#Bean
public IntegrationFlow sayHelloFlow(){
String uri = "http://localhost:8081/hellos";
return IntegrationFlows.from("integration.example.gateway.channel")
.filter("headers['operation'] == 'OPERATION_A'")
.<SearchRequest>handle((request) -> {
Map<String, String> header = new HashMap<String, String>();
header.put("a_header", request.getHeaders().get("initial_val", String.class));
SearchRequestB obj = new SearchRequestB(
request.getPayload()+"Modified",
header);
})
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
}
My IntegrationGateway class is
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
#MessagingGateway
public interface IntegrationGateway {
#Gateway(requestChannel = "integration.example.gateway.channel")
public String canSearch(String message);
}
In the above problem is once I remove handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST)) line it works properly. and by keeping same line I am getting following error
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.integration.dsl.IntegrationFlow]: Factory method 'sayHelloFlow' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: The 'currentComponent' (com.integration.config.IntegrationConfig$$Lambda$793/0x0000000800567440#340cb97f) is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. This is the end of the integration flow.
I am wanted to call the another REST end point within this handle method.
What is wrong I am doing here
Thanks in advance !!
Your problem that handle(Message<?>) is really a one-way endpoint with a void return type and can be used only in the end of flow. That’s what that error about : since this endpoint cannot produce reply, there is no way to call the next endpoint in the flow. It is more suspicious that your code introduces that obj variable and does nothing with it.
See more in docs : https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-class-cast.
To fix your solution we need to know if you want to call a rest with that obj or in parallel with this custom lambda.

Handling errors coming from gateway implementation in the use case - Clean Architecture

how can I handle exceptions coming from the gateway implementation when we are building software using the Onion Architecture?
To clarify, I've created an example using Java and SpringBoot:
#Component
#AllArgsConstructor
public class SaveAddressUseCase{
private final GetCustomerGateway getCustomerGateway;
public void execute(AddressDomain address, Long customerId){
try{
//validates the customerId supplied and returns the customer from an external service.
CustomerDomain customer = getCustomerGateway.execute(customerId);
address.setCustomer(customer);
//saves the address
}catch(CustomerNotFoundException ex) {
AddressErrorDomain addressErrorDomain = new AddressErrorDomain();
//saves the address in a error table with httpStatus 404
} catch (InternalErrorException ex) {
AddressErrorDomain addressErrorDomain = new AddressErrorDomain();
//save the address in a error table with httpStatus 500
}
}
}
This is a simple useCase that will save an address but first, it needs to get the customer of this address from an external service. If the customer is not found, I need to save the address in an error table to processes it later. The same goes if this external service is down, but it's important to differentiate between these two errors and I can handle this problem using the HttpStatus returned from my API call.
public interface GetCustomerGateway {
CustomerDomain execute(Long customerId);
}
#Component
#AllArgsConstructor
public class GetCustomerGatewayImpl implements GetCustomerGateway {
private final CustomerApi customerApi; //Feign interface to call an external service
public CustomerDomain execute(Long customerId){
try{
return customerApi.getCustomerById(customerId);
}catch(FeignException ex){
if (ex.status() == HttpStatus.NOT_FOUND.value()) {
throw new CustomerNotFoundException();
} else {
throw new InternalErrorException();
}
}
}
}
Lastly, this is my gateway implementation that just makes a call to this external service using a simple Feign interface and throws two custom exceptions that I extended from RuntimeException.
Question: Catching these two exceptions in the usecase I'm not dealing with details that only the gateway must know? Or even worse, I'm not using exceptions to control the flow of my application? How can I handle the errors coming from the Gateway implementation in a better way than I did in my example?
Obs: In this example, it's important to save the address in error table to not ruins the user experience in the client-side, and I also need to differentiate between these errors.
Thanks in advance!
Consider using #ControllerAdvice for this to keep the controller clean and focused
#ControllerAdvice
#Slf4j
public class RestExceptionHandler {
//Magic happens here
}
Inside RestExceptionHandler, you can catch all feign exceptions like this and handle them however you want
#ResponseBody
#ExceptionHandler(Throwable.class)
public final ResponseEntity<?> handleFeignExceptions(Exception ex, WebRequest request) {
if (ex instanceof FeignException) {
return handle((FeignException) ex);// define your custom handle method
}
}

Feign Hystrix command name not working

If I just have a Hystrix Command defined as class, i have control over defining the group key and command key like below.
private static class MyHystrixCommand extends HystrixCommand<MyResponseDto> {
public MyHystrixCommand() {
super(HystrixCommandGroupKey.Factory.asKey("MyHystrixGroup"));
}
So for the above code group key is MyHystrixGroup and Command Key is MyHystrixCommand.
If i want to set any configurations of this hystrix command i can do like
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.MyHystrixCommand.execution.timeout.enabled", false);
Where as the default ones will be,
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.execution.timeout.enabled", false);
Now when I am using Feign Hystrix, I am not defining the command name/ group name. As per the documentation here, the group key matches the target name and command key are same as logging keys.
So if I have a FeignClient like this,
interface TestInterface {
#RequestLine("POST /")
String invoke() throws Exception;
}
I create the instance of my Feign client in a factory class.
class TestFactory {
public TestInterface newInstance() {
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 500);
return HystrixFeign.builder()
.target(TestInterface.class, "http://localhost:" + server.getPort(), (FallbackFactory) new FallbackApiRetro());
}
}
As you see before returning the client, i want to set the timeout configuration of my hystrix command.
I am testing it with a MockWebServer.
#Test
public void fallbackFactory_example_timeout_fail() throws Exception {
server.start();
server.enqueue(new MockResponse().setResponseCode(200)
.setBody("ABCD")
.setBodyDelay(1000, TimeUnit.MILLISECONDS));
TestFactory factory = new TestFactory();
TestInterface api = factory.newInstance();
// as the timeout is set to 500 ms, this case should fail since i added 1second delay in mock service response.
assertThat(api.invoke()).isEqualTo("Fallback called : foo");
}
This is working only when i set the time out on default hystrix paramater
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.invoke.execution.isolation.thread.timeoutInMilliseconds", 500);
This didn't work.
Similarly i tried below values none of them worked.
hystrix.command.TestInterface#invoke(String).execution.isolation.thread.timeoutInMilliseconds
hystrix.command.TestInterface#invoke.execution.isolation.thread.timeoutInMilliseconds
I figured it out.
ConfigurationManager.getConfigInstance().setProperty("hystrix.command.TestInterface#invoke().execution.isolation.thread.timeoutInMilliseconds",500);
is working. The mistake i did was my method name was not having any parameters passed in. So for a feign hystrix client, the command name is
FeignClientInterfaceName#MethodNameWithSignatures
For example quoted in the question, it is
TestInterface#invoke()

How to send a message to an actor from outside in Play Framework 2?

I am new to Akka and trying to write some code in Play Framework 2 in Java and use Akka.
To create an actor and send a test message to it, I have:
public class Global extends GlobalSettings {
#Override
public void onStart(Application app) {
final ActorRef testActor = Akka.system().actorOf(Props.create(TestActor.class), "testActor");
testActor.tell("hi", ActorRef.noSender());
}
}
This work perfectly fine and I see that my actor received my message, here is the code for my actor:
public class TestActor extends UntypedActor {
#Override
public void onReceive(Object message) throws Exception {
if(message.toString().equals("hi")){
System.out.println("I received a HI");
}else{
unhandled(message);
}
}
}
Very simple.
However, If I try to send a message from a controller:
public static Result index() {
final ActorRef testActor = Akka.system().actorFor("testActor");
testActor.tell("hi", ActorRef.noSender());
return ok(index.render("Your new application is ready."));
}
I get this message on terminal:
[INFO] [09/20/2014 11:40:30.850] [application-akka.actor.default-dispatcher-4] [akka://application/testActor] Message [java.lang.String] from Actor[akka://application/deadLetters] to Actor[akka://application/testActor] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
Can someone help me with this? why the first usage works and the second one fails? Thanks
The actorFor method requires the entire path, and your actor lives in the user space, so you have to use actorFor("/user/testActor"). Currently you are sending it to application/testActor, which would be a top-level actor in the ActorSystem itself.
By the way, actorFor is deprecated (at least in the Scala API) and replaced by actorSelection.
For more information, refer to the excellent documentation.
actorFor should get the path to the actor which is probably "akka://System/user/testActor". It also does not create the actor, meaning it should be exist.
Anyway, is there a reason that inside the controller you use the actorFor and not the actorOf? It had been deprecated and shouldn't be use.

Categories

Resources