Handling Concurrent Service Calls in a Spring Boot Application: CompletableFuture and @Async
- 4.2/5
- 489
- Nov 16, 2024
In modern e-commerce applications, service endpoints often need to consolidate data from multiple sources. Imagine an API to retrieve an order summary, gathering details from services like order-service, customer-service, and inventory-service, along with a local method call for payment details.
Efficient handling of these concurrent calls is crucial for performance, and Spring Boot offers two primary approaches: asynchronous programming using CompletableFuture (for Spring MVC) and reactive programming using WebClient (for Spring WebFlux).
Here, we explore CompletableFuture approach, highlighting the advantages and implementations in a Spring Boot e-commerce context.
Asynchronous Programming with CompletableFuture and @Async in Spring Web
This approach leverages the @Async annotation and ompletableFuture to make asynchronous calls to the order-service, customer-service, and inventory-service in a Spring Web application.
1) Enable Async Support
First, ensure that your Spring Boot application is configured to support asynchronous processing by adding @EnableAsync in the main application class.
2) Create Client classes
In these service classes, we will make asynchronous HTTP calls to the order-service, customer-service, and inventory-service.
3) Internal method call for Payment Info
This PaymentService demonstrates an internal call to calculate payment logic. It introduces a 500-millisecond delay to simulate the time it might take to fetch or calculate payment data from an external system or database.
4) Create a order aggregation service class
This service will call the asynchronous service clients and consolidate the results.
5) Create a order aggregation controller class
In your controller, call aggregateOrderData and return the result.
6) Add external services URLs in application.properties
These are the external service URLs to be used in the respective client to call endpoints.
7) Create "order-service", "customer-service", and "inventory-service"
These are really simple Spring Boot services with only one endpoint, so there is no point in discussing them in detail. Please find below the repo link for them.
1) The order-service
Repo link: GitHub
Swagger: http://localhost:8081/swagger-ui.html
2) The customer-service
Repo link: GitHub
Swagger: http://localhost:8082/swagger-ui.html
3) The inventory-service
Repo link: GitHub
Swagger: http://localhost:8083/swagger-ui.html
Each service has one endpoint that simulates a delay by using Thread.sleep().
8) Test the application
Run your Spring Boot application and navigate to http://localhost:8080/swagger-ui/ to access the Swagger UI. This will allow you to test your endpoints interactively.
2024-11-11T18:18:14.343+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [nio-8080-exec-5] com.cb.controller.OrderController : Fetching order summary for order id: 560ac632-bee9-4322-b204-c4f77e0e2adc, customer id: 77f67c24-19af-4f85-b9d1-561a2afe47e7, product id: 4eea9612-0c17-4ce3-9fd7-689d7b9c01b9
2024-11-11T18:18:14.359+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-1] com.cb.client.order.OrderClient : Fetching order from order service for order id: 560ac632-bee9-4322-b204-c4f77e0e2adc
2024-11-11T18:18:14.359+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-2] com.cb.client.customer.CustomerClient : Fetching customer from customer service for customer id: 77f67c24-19af-4f85-b9d1-561a2afe47e7
2024-11-11T18:18:14.359+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-3] c.cb.client.inventory.InventoryClient : Fetching inventory from inventory service for product id: 4eea9612-0c17-4ce3-9fd7-689d7b9c01b9
2024-11-11T18:18:14.931+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-3] c.cb.client.inventory.InventoryClient : Inventory fetched successfully for product id: 4eea9612-0c17-4ce3-9fd7-689d7b9c01b9
2024-11-11T18:18:14.931+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-2] com.cb.client.customer.CustomerClient : Customer fetched successfully for customer id: 77f67c24-19af-4f85-b9d1-561a2afe47e7
2024-11-11T18:18:14.931+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-1] com.cb.client.order.OrderClient : Order fetched successfully for order id: 560ac632-bee9-4322-b204-c4f77e0e2adc
2024-11-11T18:18:14.932+05:30 INFO 6942 --- [concurrent-service-calls-spring-boot-completable-future] [ task-1] com.cb.controller.OrderController : Order summary fetched successfully for order id: 560ac632-bee9-4322-b204-c4f77e0e2adc, customer id: 77f67c24-19af-4f85-b9d1-561a2afe47e7, product id: 4eea9612-0c17-4ce3-9fd7-689d7b9c01b9 in 590 ms
From the logs, you can see that the entire process took approximately 600 ms. Without parallel calls, it would have taken more than 2000 ms, with about 500 ms for each external service call as well as one internal method call.
Source code: GitHub