-
Notifications
You must be signed in to change notification settings - Fork 54.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17574 from pedrolopes9-7/BAEL-8300
BAEL-8300: Using CompletableFuture With Feign Client in Spring Boot
- Loading branch information
Showing
6 changed files
with
188 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
...n/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PaymentMethodClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.baeldung.cloud.openfeign.completablefuturefeignclient; | ||
|
||
import org.springframework.cloud.openfeign.FeignClient; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
|
||
@FeignClient(name = "paymentMethodClient", url = "http://localhost:8083") | ||
public interface PaymentMethodClient { | ||
|
||
@RequestMapping(method = RequestMethod.GET, value = "/payment_methods") | ||
String getAvailablePaymentMethods(@RequestParam(name = "site_id") String siteId); | ||
} |
50 changes: 50 additions & 0 deletions
50
.../main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package com.baeldung.cloud.openfeign.completablefuturefeignclient; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import org.springframework.stereotype.Service; | ||
|
||
import feign.FeignException; | ||
import feign.RetryableException; | ||
|
||
@Service | ||
public class PurchaseService { | ||
|
||
private final PaymentMethodClient paymentMethodClient; | ||
private final ReportClient reportClient; | ||
|
||
public PurchaseService(PaymentMethodClient paymentMethodClient, ReportClient reportClient) { | ||
this.paymentMethodClient = paymentMethodClient; | ||
this.reportClient = reportClient; | ||
} | ||
|
||
public String executePurchase(String siteId) throws ExecutionException, InterruptedException { | ||
CompletableFuture<String> paymentMethodsFuture = CompletableFuture.supplyAsync(() -> paymentMethodClient.getAvailablePaymentMethods(siteId)) | ||
.orTimeout(400, TimeUnit.MILLISECONDS) | ||
.exceptionally(ex -> { | ||
if (ex.getCause() instanceof FeignException && ((FeignException) ex.getCause()).status() == 404) { | ||
return "cash"; | ||
} | ||
|
||
if (ex.getCause() instanceof RetryableException) { | ||
// handle REST timeout | ||
throw new RuntimeException("REST call network timeout!"); | ||
} | ||
|
||
if (ex instanceof TimeoutException) { | ||
// handle thread timeout | ||
throw new RuntimeException("Thread timeout!", ex); | ||
} | ||
|
||
throw new RuntimeException("Unrecoverable error!", ex); | ||
}); | ||
|
||
CompletableFuture.runAsync(() -> reportClient.sendReport("Purchase Order Report")) | ||
.orTimeout(400, TimeUnit.MILLISECONDS); | ||
|
||
return String.format("Purchase executed with payment method %s", paymentMethodsFuture.get()); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...src/main/java/com/baeldung/cloud/openfeign/completablefuturefeignclient/ReportClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.baeldung.cloud.openfeign.completablefuturefeignclient; | ||
|
||
import org.springframework.cloud.openfeign.FeignClient; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
|
||
@FeignClient(name = "reportClient", url = "http://localhost:8083") | ||
public interface ReportClient { | ||
|
||
@RequestMapping(method = RequestMethod.POST, value = "/reports") | ||
void sendReport(@RequestBody String reportRequest); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
...baeldung/cloud/openfeign/completablefuturefeignclient/PurchaseServiceIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.baeldung.cloud.openfeign.completablefuturefeignclient; | ||
|
||
import com.baeldung.cloud.openfeign.ExampleApplication; | ||
import com.github.tomakehurst.wiremock.WireMockServer; | ||
|
||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Disabled; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.test.context.junit.jupiter.SpringExtension; | ||
|
||
import java.util.concurrent.ExecutionException; | ||
|
||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.get; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.post; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; | ||
import static org.junit.Assert.*; | ||
|
||
@ExtendWith(SpringExtension.class) | ||
@SpringBootTest(classes = ExampleApplication.class) | ||
class PurchaseServiceIntegrationTest { | ||
|
||
@Autowired | ||
private PurchaseService purchaseService; | ||
|
||
@Autowired | ||
private PaymentMethodClient paymentMethodClient; | ||
|
||
@Autowired | ||
private ReportClient reportClient; | ||
|
||
private WireMockServer wireMockServer; | ||
|
||
@BeforeEach | ||
public void startWireMockServer() { | ||
wireMockServer = new WireMockServer(8083); | ||
configureFor("localhost", 8083); | ||
wireMockServer.start(); | ||
|
||
stubFor(post(urlEqualTo("/reports")).willReturn(aResponse().withStatus(HttpStatus.OK.value()))); | ||
} | ||
|
||
@AfterEach | ||
public void stopWireMockServer() { | ||
wireMockServer.stop(); | ||
} | ||
|
||
@Test | ||
void givenRestCalls_whenBothReturnsOk_thenReturnCorrectResult() throws ExecutionException, InterruptedException { | ||
stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withStatus(HttpStatus.OK.value()) | ||
.withBody("credit_card"))); | ||
|
||
String result = purchaseService.executePurchase("BR"); | ||
|
||
assertNotNull(result); | ||
assertEquals("Purchase executed with payment method credit_card", result); | ||
} | ||
|
||
@Test | ||
void givenRestCalls_whenPurchaseReturns404_thenReturnDefault() throws ExecutionException, InterruptedException { | ||
stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); | ||
|
||
String result = purchaseService.executePurchase("BR"); | ||
|
||
assertNotNull(result); | ||
assertEquals("Purchase executed with payment method cash", result); | ||
} | ||
|
||
@Test | ||
@Disabled | ||
void givenRestCalls_whenPurchaseCompletableFutureTimeout_thenThrowNewException() { | ||
stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withFixedDelay(550))); | ||
|
||
Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR")); | ||
|
||
assertEquals("java.lang.RuntimeException: Thread timeout!", error.getMessage()); | ||
} | ||
|
||
@Test | ||
void givenRestCalls_whenPurchaseRequestWebTimeout_thenThrowNewException() { | ||
stubFor(get(urlEqualTo("/payment_methods?site_id=BR")).willReturn(aResponse().withFixedDelay(250))); | ||
|
||
Throwable error = assertThrows(ExecutionException.class, () -> purchaseService.executePurchase("BR")); | ||
|
||
assertEquals("java.lang.RuntimeException: REST call network timeout!", error.getMessage()); | ||
} | ||
} |