API calls with Reactive Spring Framework
In my previous article, I showed simple CRUD operation with Reactive Spring. If you haven’t checked it out, then you can check it out here.
In this article, I’ll be covering how API calls can be made with reactive spring framework. I’ll use my project in previous article in which I created apis to perform CRUD operations as a server and we’ll build a client for it to call those APIs.
Project Setup
This project will only include reactive web
as a dependency.
Perform API calls
We’ll start with building a WebClientHelper
, where we’ll create generic functions for performing different type of HTTP calls (like POST, PUT, GET etc).
WebClientHelper
will contain one field as WebClient
which will help us in performing the api calls. We’ll instantiate the webClient field in the constructor.
public class WebClientHelper {
private WebClient webClient;
public WebClientHelper() {
webClient = WebClient.create();
}}
Now as our basic setup for WebClientHelper
is done. Let’s get started by building a simple function to perform GET api call which will fetch a single object.
GET
For building request to perform any api call, firstly, we’ll require an address to the server and a path to which we are calling. Secondly, we may need to add parameters/request body if required. And finally we’ll need to cast the response of the api into some response model to use it.
So 3 things required to perform api call will be
- URI — comprising of server address and path
- Query Parameter/request body
- Response Model
public <T> Mono<T> performGetToMono(URI uri, MultiValueMap<String, String> params, Class<? extends T> clazzResponse){
return webClient.get()
.uri(uriBuilder -> uriBuilder
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.path(uri.getPath())
.queryParams(params)
.build()
)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
In the above function, we use webClient
object to perform get operation and then with uri()
we build whole address by combining server address(host and port), path and query parameters. Then we call exchange()
which actually performs a HTTP call to the address and with the data we provided. After retrieving the response from the server we convert response into our model class using bodyToMono()
. Similarly we want to fetch a list of objects then we can use bodyToFlux()
method to convert to list of objects. Below is the illustration of fetching more than one object.
public <T> Flux<T> performGetToFlux(URI uri, MultiValueMap<String, String> params, Class<? extends T> clazzResponse){
return webClient.get()
.uri(uriBuilder -> uriBuilder
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.path(uri.getPath())
.queryParams(params)
.build()
)
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(clazzResponse));
}
In reactive spring, Mono is used for zero or one object and Flux refers to zero or more than one objects.
POST
We can look into a POST request which will include a request body in the request and give a response in the form of one object.
public <T> Mono<T> performPostToMono(URI uri, Object requestBody, Class<? extends T> clazzResponse){
return webClient.post()
.uri(uriBuilder -> uriBuilder
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.path(uri.getPath())
.build()
)
.body(BodyInserters.fromValue(requestBody))
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
body()
is used to attach the request body in HTTP request which we want to perform. Here, we use post()
to indicate that we want to perform a POST request.
We can quickly have a look for performing PUT and DELETE function which will be similar to above examples of GET and POST.
PUT
public <T> Mono<T> performPutToMono(URI uri, Object requestBody, Class<? extends T> clazzResponse){
return webClient.put()
.uri(uriBuilder -> uriBuilder
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.path(uri.getPath())
.build()
)
.body(BodyInserters.fromValue(requestBody))
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
DELETE
public <T> Mono<T> performDeleteToMono(URI uri, MultiValueMap<String, String> params, Class<? extends T> clazzResponse){
return webClient.delete()
.uri(uriBuilder -> uriBuilder
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.path(uri.getPath())
.queryParams(params)
.build()
)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
EmployeeModel
We can now add a EmployeeModel similar to the response which we are getting from server.
public class EmployeeModel {
private Long id;
private String name;
private String department;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
Now we can add a controller which will trigger API calls to other service and fetch results and display as a response.
Controller
We’ll build 5 endpoints similar to other service where we have our CRUD operation APIs. We’ll use our WebClientHelper
to perform HTTP request and the value returned from the helper after performing the HTTP request we’ll send back as the response from this service.
@RestController
@RequestMapping("/employeeClient")
public class EmployeeClientController {
@Value("${employee.server.host}")
private String employeeHost;
@Autowired
private WebClientHelper webClientHelper;
@GetMapping("/{employeeId}")
public Mono<EmployeeModel> getEmployeeById(@PathVariable("employeeId") String employeeId){
String url = ApiPaths.getEmployeePath(employeeHost) + "/" + employeeId;
return webClientHelper.performGetToMono(URI.create(url), null, EmployeeModel.class);
}
@GetMapping
public Flux<EmployeeModel> getEmployeeList(){
return webClientHelper.performGetToFlux(URI.create(ApiPaths.getEmployeePath(employeeHost)), null, EmployeeModel.class);
}
@PostMapping
public Mono<EmployeeModel> saveEmployee(@RequestBody EmployeeModel employeeModel){
return webClientHelper.performPostToMono(URI.create(ApiPaths.getEmployeePath(employeeHost)), employeeModel,
EmployeeModel.class);
}
@PutMapping
public Mono<EmployeeModel> updateEmployee(@RequestBody EmployeeModel employeeModel){
return webClientHelper
.performPutToMono(URI.create(ApiPaths.getEmployeePath(employeeHost)), employeeModel,
EmployeeModel.class);
}
@DeleteMapping("/{id}")
public Mono<EmployeeModel> deleteEmployee(@PathVariable("id") Long employeeId){
String url = ApiPaths.getEmployeePath(employeeHost) + "/" + employeeId;
return webClientHelper.performDeleteToMono(URI.create(url),null, EmployeeModel.class);
}
}
application.properties
employee.server.host=http://localhost:8080/
This key is added in application.properties which refer to the employee service which will we’ll call from this employee client service.
That’s it !
We have successfully build a client to perform HTTP request to other services.
Code is available on github :
Employee Client built in this article - https://github.com/rsbeoriginal/ApiCallUsingReactiveSpring
Employee service with CRUD operation - https://github.com/rsbeoriginal/r2dbc