How to Save Multiple Records using Web flux and JDBC? - spring-boot

I am trying to build a simple web application using spring boot - webflux (functional endpoints) & jdbc. The app receives payload in XML format (which is some details of 1 employee). Code given below persists data for one employee as expected.
public Mono<String> createData(final Mono<Record> inputMono) {
final String someID = UUID.randomUUID().toString();
final Mono<Integer> asyncUpdate = inputMono.flatMap(record -> {
return beginUpdate(dataSource,
sqlStatementSharedAbove).withStatementEnricher(stmt -> {
stmt.setString(1, record.getFirstName());
stmt.setString(2, record.getLastName());
stmt.setInt(3, record.getAddress());
}).build();
});
return asyncUpdate.doOnSuccess(affectedRows -> LOGGER.debug("Added
{} rows with ID {}", affectedRows, someID))
.map(affectedRows -> someID);
}
Now I need to save similar data for multiple employees (modifying the XML payload to contain multiple employee records)
In non-webflux world, I would just iterate over the list of employee objects and call this function for each one of them.
How can I achieve the same in webflux?
Essentially I am looking to handle a saveAll functionality with webflux and given that I have to work with JDBC (I do understand that JDBC doesn't support non blocking paradigm and Mongo supports a saveAll API but I have certain constraints as to what DB i can use and therefore must make this work with JDBC)
Thank you.

Following code works to save multiple employee records. Essentially it needs a Flux (of Employees) to work with -
public Mono<Void> createData(final Flux<Record> inputFlux) {
return inputFlux.flatMap(record -> {
return beginUpdate(dataSource,
sqlStatementSharedAbove).withStatementEnricher(stmt -> {
stmt.setString(1, record.getFirstName());
stmt.setString(2, record.getLastName());
stmt.setInt(3, record.getAddress());
}).build().doOnSuccess(affectedRows -> LOGGER.info("Added rows{}", affectedRows));
}).then;
}

Related

is it possible to call a Microservice (spring boot) from a Camunda service Task?

Workflow -> (https://i.stack.imgur.com/vgtiD.png)
Is it possible to call a microservice from a Camunda task?
1.The start event will received a Json with a client data .
2.The service task should connect to a microservice (spring boot) that stores the data in the database.-> just need to pass the json with the info to the micro and then should complete the task.
3. if the previous task is completed this task should run.
is there a way to do it? I am very new at camunda.
External Task but it didnt work
Yes you can, check for documentation :
#Component
#ExternalTaskSubscription("scoreProvider") // create a subscription for this topic name
public class ProvideScoreHandler implements ExternalTaskHandler {
#Override
public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) {
// only for the sake of this demonstration, we generate random data
// in a real-world scenario, we would load the data from a database
String customerId = "C-" + UUID.randomUUID().toString().substring(32);
int creditScore = (int) (Math.random() * 11);
VariableMap variables = Variables.createVariables();
variables.put("customerId", customerId);
variables.put("creditScore", creditScore);
// complete the external task
externalTaskService.complete(externalTask, variables);
Logger.getLogger("scoreProvider")
.log(Level.INFO, "Credit score {0} for customer {1} provided!", new Object[]{creditScore, customerId});
}
}
Spring boot with Camunda example

How to write a simple business logic in spring web flux mono

This simple logic I want to take a mono locality object from AddressRepository and if the locality exist it will update else new record will be added into locality table.
As I am very new to spring reactive programming, I am little confuse about the implementation. I have written following code segments.
Address address = new Address();
// If locality getByName == true -> Update
this.addressRepository.findByName(addressCommand.getLocality())
.convert().with(toMono())
.subscribe(item -> {
if(item.getLocalityName().equalsIgnoreCase(addressCommand.getLocality())){
address.setLocality(item);
}else{
address.setLocality(Locality.builder()
.localityName(addressCommand.getLocality()).build());
}
});
In the same method I have another where set the above address and return user Id to client.
Mono<User> createdUser = this.userRepository.adminCreate(
User.builder()
.address(address)
.administrativeArea(administrativeArea)
.build()
)
.convert().with(toMono());
return createdUser.map(u -> u.getCustomerId().toString());
If I execute this in debug mode and if I put debug points I can see user data setting doing first and after some time address setting happening. So always locality data will be null. I wonder can I do this within same pipeline or any suggestions?
Thanks,
Dasun.

How to extract content from Mono<List<T>> in WebFlux to pass it down the call chain

I want to be able to extract the List<Payload> from the Mono<List<Payload>> to pass it to a downstream service for processing (or maybe return from the read(RequestParams params) method, instead of it returning void):
#PostMapping("/subset")
public void read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
....
}
where reader.read(...) is a method on an autowired Spring service utilizing a webClient to get the data from external web service API:
public Mono<List<Payload>> read(String date, String assetClasses, String firmAccounts, String id, String password) {
Flux<Payload> nodes = client
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/subset")
.queryParam("payloads", true)
.queryParam("date", date)
.queryParam("assetClasses", assetClasses)
.queryParam("firmAccounts", firmAccounts)
.build())
.headers(header -> header.setBasicAuth("abc123", "XXXXXXX"))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx error");
return Mono.error(new RuntimeException("4xx"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx error");
return Mono.error(new RuntimeException("5xx"));
})
.bodyToFlux(Payload.class);
Mono<List<Payload>> records = nodes
.collectList();
return records;
}
Doing a blocking result.block() is not allowed in WebFlux and throws an exception:
new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread ..." ;
What is a proper way to extract the contents of a Mono in WebFlux?
Is it some kind of a subscribe()? What would be the syntax?
Thank you in advance.
There is no "proper way" and that is the entire point. To get the value you need to block, and blocking is bad in webflux for many reasons (that I won't go into right now).
What you should do is to return the publisher all the way out to the calling client.
One of the things that many usually have a hard time understanding is that webflux works with a producer (Mono or Flux) and a subscriber.
Your entire service is also a producer, and the calling client can be seen as the subscriber.
Think of it as a long chain, that starts at the datasource, and ends up in the client showing the data.
A simple rule of thumb is that whomever is the final consumer of the data is the subscriber, everyone else is a producer.
So in your case, you just return the Mono<List<T> out to the calling client.
#PostMapping("/subset")
public Mono<List<Payload>> read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result;
}
While the following does return the value of the Mono observable in the logs:
#PostMapping("/subset")
#ResponseBody
public Mono<ResponseEntity<List<Payload>>> read1(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result
.map(e -> new ResponseEntity<List<PayloadByStandardBasis>>(e, HttpStatus.OK));
}
the understanding I was seeking was a proper way to compose a chain of calls, with WebFlux, whereby a response from one of its operators/legs (materialized as as a result from a webclient call, producing a set of records, as above) could be passed downstream to another operator/leg to facilitate a side effect of saving those records in a DB, or something to that effect.
It would probably be a good idea to model each of those steps as a separate REST endpoint, and then have another endpoint for a composition operation which internally calls each independent endpoint in the right order, or would other design choices be more preferred?
That is ultimately the understanding I was looking for, so if anyone wants to share an example code as well as opinions to better implement the set of steps described above, I'm willing to accept the most comprehensive answer.
Thank you.

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-kqueue-4

Im getting an error with the blocking operation in Spring Webflux. I retrieve a Mono of list of Address documents and im using this Mono list of address documents to form the street address(withStreet)as shown below :
Mono<List<Address>> docs = getAddress(id, name);
AddressResponse addrResponse = new AddressResponse.Builder().
withStreet(docs.map(doc -> doc.stream().
map(StreetAddress::map).
collect(Collectors.toList())).block()).
build();
map method :
public static StreetAddress map(Address addr) {
return new Builder().
withId(addr.getId()).
withStreet(addr.getStreetAddress()).
build();
}
When i execute the above code, it throws a "block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2". Could you please suggest how to fix. i want to retrieve AddressResponse without blocking it. This response will be further used in the code in Response Entity as shown below :
return Mono.just(ResponseEntity
.status(HttpStatus.OK)
.body(addrResponse)));
The problem is you try to mix reactive and imperative code.
Instead, just map it in the reactive pipeline:
Mono<AddressResponse> response = docs.map(addresses->{
return new AddressResponse.Builder()
.withStreet(addresses -> addresses.stream()
.map(StreetAddress::map)
.collect(Collectors.toList()))
.build();
})
Then you can return it as is, or map it into a Mono> type, apply the same method then above.

Multiple Mono and common subscriber

I am a newbie to reactive programming in Java. I plan to use spring-webclient instead of restclient as the latter is being decommissioned. I have a situation when I make several http post requests to different endpoints and the response structure is identical. With webclient code as below,
List<Mono<CommonResponse>> monolist = new ArrayList<>();
for(String endpoint : endpoints) {
Mono<CommonResponse> mono = webClient.post()
.uri(URI.create(endPoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class);
monolist.add(mono);
}
I get a mono per request. As the response is common, I would like each mono to be subscribed a common method, but how can I distinguish the endpoints, assuming that the response data is not helping.
Can I pass additional arguments to the method while subscribing?
You can do this in following way. If you have many monos you can treat team as flux which actually means that you have many of Mono. Then you can subscribe all of them with single method. To pass to subscribing method some extra arguments like information about endpoint, you can create extra object with additional information.
Flux<ResponseWithEndpoint> commonResponseFlux = Flux.fromIterable(endpoints)
.flatMap(endpoint -> webClient.post()
.uri(URI.create(endpoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class)
.map(response -> new ResponseWithEndpoint(response, endpoint)));
...
class ResponseWithEndpoint {
CommonResponse commonResponse;
String endpoint;
public ResponseWithEndpoint(CommonResponse commonResponse, String endpoint) {
this.commonResponse = commonResponse;
this.endpoint = endpoint;
}
}

Resources