Spring webflux - multi Mono - spring-boot

I do not know or ask this question except here, if it's not the place I apologize.
I am currently working on an application using spring webflux and I have a problem with the use of Mono and Flux.
Here I have a REST request that comes with a simple bean that contains attributes including a list. This list is iterated to use a responsive mongo call that returns a Mono (findOne).
But I do not think I've found the right way to do it:
#PostMapping
#RequestMapping("/check")
public Mono<ContactCheckResponse> check(#RequestBody List<ContactCheckRequest> list) {
final ContactCheckResponse response = new ContactCheckResponse();
response.setRsnCode("00");
response.setRspnCode("0000");
LOG.debug("o--> person - check {} items", list.size());
final List<ContactCheckResponse.Contact> contacts = new ArrayList<>();
response.setContacts(contacts);
return Mono.fromCallable(() -> {
list.stream().forEach( c -> {
Boolean exists = contactRespository.findOneByThumbprint(c.getIdentifiant()).block() != null;
ContactCheckResponse.Contact responseContact = new ContactCheckResponse.Contact();
responseContact.setExist(exists);
responseContact.setIdentifiant(c.getIdentifiant());
responseContact.setRsnCode("00");
responseContact.setRspnCode("0000");
response.getContacts().add(responseContact);
});
return response;
});
}
the fact of having to make a block does not seem to me in the idea "reactive" but I did not find how to do otherwise.
Could someone help me find the best way to do this task?
Thank you

Something along these lines:
return Flux.fromIterable(list)
.flatMap(c -> contactRespository.findOneByThumbprint(c.getIdentifiant())
.map(r -> r != null)
.map(exists -> {
ContactCheckResponse.Contact responseContact = new ContactCheckResponse.Contact();
...
return responseContact;
})
)
.reduce(response, (r,c) -> {
response.getContacts().add(responseContact);
return response;
});
Create a Flux from the list, create a contact for each entry and reduce everything to a Mono.

Related

Handle multiple external services using project reactor

I am new to reactive programming and trying to simulate the below use case using project reactor but I see little bit difficult to pass the response from one service call to another dependent service. Any suggestions or references will be highly appreciated.
Response getDetails(Request inputRequest){
//Call two external services parallel based on the incoming request
Response1 = callExternalService1(inputRequest)
Response2 = callExternalService2(inputRequest)
//Call one external service based on the Response1 and Response2
Response3 = callExternalService3(Response1,Response2);
//Call three external service parallel based on the Response1, Response2 and Response3
Response4 = callExternalService4(Response1,Response2,Response3);
Response5 = callExternalService5(Response1,Response2,Response3);
Response6 = callExternalService6(Response1,Response2,Response3);
//Last service call
Response finalResponse= callLastExternalService(Response4,Response5,Response6);
return finalResponse;
}
I tried the below sample and it's working for one service call but not able to pass the response to other dependent service calls.
Updated answer:
Mono<Response> getDetails(Request inputRequest){
return Mono.just(inputRequest.getId())
.flatMap(id->{
DbResponse res = getDBCall(id).block();
if(res == null){
return Mono.error(new DBException(....));
}
return Mono.zip(callExternalService1(res),callExternalService2(inputRequest));
}).flatMap(response->{
Response extser1 = response.getT1();
Response extser2 = response.getT2();
//any exceptions?
return Mono.zip(Mono.just(extser1),Mono.just(extser2),callExternalService3();
}).flatMap(response->callExternalService4(response.getT1(),response.getT2(),response.getT3())
});
}
private Mono<DbResponse> getDBCall(String id) {
return Mono.fromCallable(()->dbservice.get(id))
.subscribeOn(Schedulers.boundedElastic());
}
Questions:
How to convert Mono<DbResponse> to DbResponse without using block
operation?
If any of the external service failed, how to build the
failure response inside the flatmap and return back?
If you have n calls and you want to move in steplock (that is, move ahead iff you have the response from all the calls), use zip. For instance:
Mono.zip(call1, call2)
.flatMap(tuple2 -> {
ResponseEntity<?> r1 = tuple2.getT1(); //response from call1
ResponseEntity<?> r2 = tuple2.getT2(); //response from call2
return Mono.zip(Mono.just(r1), Mono.just(r2), call3);
})
.flatMap(tuple3 -> {
//at this point, you have r1, r2, r3. tuple3.getT1() response from call 1
return Mono.zip(call4, call5, call6); //tuple3.getT2() response from call 2, tuple3.getT3() response from call3
})
.flatMap(tuple3 -> callLastService);
Note: If is more of a pseudo-code, won't compile right away
You can extend the above to answer your own question. Note that since call1 and call2 are independent, you can run them parallely using subscribeOn(Schedulers.boundedElastic())
Edit: Answering the two follow-up questions:
No need to subscribe using block() as flatMap subscribes to your inner streams eagerly. You can do something like:
Mono.just(inputRequest.getId())
.flatMap(a -> getDBCall(a).switchIfEmpty(Mono.defer(() -> Mono.error(..))))
Note: Mono.callable(..) returns an empty stream if the callable returns empty. That's why switchIfEmpty
You can use operators like onErrorResume to provide a fallback stream. See: The difference between onErrorResume and doOnError
if your services return Mono of Response (otherwise you have to transform them), you can make parllel calls using zip :
Mono.zip( callExternalService1( inputRequest ),
callExternalService2( inputRequest ) )
.flatMap( resp1AndResp2 -> this.callExternalService3( resp1AndResp2.getT1(),
resp1AndResp2.getT2() )
.flatMap( response3 -> Mono.zip( callExternalService4( resp1AndResp2.getT1(),
resp1AndResp2.getT2(),
response3 ),
callExternalService5( resp1AndResp2.getT1(),
resp1AndResp2.getT2(),
response3 ),
callExternalService6( resp1AndResp2.getT1(),
resp1AndResp2.getT2(),
response3 ) )
.flatMap( resp4AndResp5AndResp6 -> callLastExternalService( resp4AndResp5AndResp6.getT1(),
resp4AndResp5AndResp6.getT2(),
resp4AndResp5AndResp6.getT3() ) ) ) );

KotlinJs - simple HTTP GET without Dynamic Type functionality

I'm totally new in KotlinJs and I wanted to check its potential in server-less service development.
I decided to start with calling external API with HTTP GET method using XMLHttpRequest() suggested in KotlinJs documentation. However, I cannot come up with any way of using it without dynamic mechanism.
fun main(args: Array<String>) {
val url = "https://jsonplaceholder.typicode.com/todos/1"
var xhttp: dynamic = XMLHttpRequest()
xhttp.open("GET", url, true)
xhttp.onreadystatechange = fun() {
if (xhttp.readyState == 4) {
println(xhttp.responseJson)
}
}
xhttp.send()
}
Of course this example works perfectly fine, but I feel like it has to be better way of doing it without disabling Kotlin's type checker.
Is there any way of doing it using KotlinJs only (without dynamic) ?
If it is not possible, could someone at least explain why?
I found a way for not using dynamic with callbacks, just like in classic .js
private fun getData(input: String, callback: (String) -> Unit) {
val url = "https://jsonplaceholder.typicode.com/todos/$input"
val xmlHttp = XMLHttpRequest()
xmlHttp.open("GET", url)
xmlHttp.onload = {
if (xmlHttp.readyState == 4.toShort() && xmlHttp.status == 200.toShort()) {
callback.invoke(xmlHttp.responseText)
}
}
xmlHttp.send()
}
and than just call it:
getData("1") {
response -> println(response)
}
Hopefully it will help someone in the future.

Stream an object, Send request with completable future and assign result to the object

I have a list of Object Signers. For each of this signers I need to make a ReST request to get their signing URL. I am trying to do it with completable futures so all ReST requests can be send in parallel, then I need to set that URL in each of the signers, so this operation will not return new signers just update the ones that I am already iterating.
I have this code that is already working, but I think that could be improved.
List<Signer> signers=......
List<CompletableFuture> futures = signers.stream()
.map(signer -> CompletableFuture.completedFuture(signer))
.map(future -> future.thenCombine( CompletableFuture.supplyAsync(()-> signatureService.getSigningUrl(future.join().getSignerId())),
(signer, url) -> {
signer.setUrl(url);
return url;
}
)).collect(toList());
futures.stream()
.map(CompletableFuture::join)
.collect(toList());
Could I replace this
futures.stream()
.map(CompletableFuture::join)
.collect(toList());
With this?
futures.stream().forEach(CompletableFuture::join)
I don't like to return that because it was already used setting it in the signer. and don't like the second collect(toList()) because I am not trying to collect anything at that time.
What other implementation would you use?
No. futures.stream().forEach(CompletableFuture::join) returns void whereas futures.stream().map(CompletableFuture::join).collect(toList()); returns CompletableFuture<List<?>>.
They both are meant for different purposes. But both does one thing common(i.e, blocks the main thread till all the completablefutures are finished).
I would write your same code bit differently using CompletableFuture.allOf.
Stream<CompletableFuture<String>> streamFutures = signers.stream()
.map(signer -> CompletableFuture.completedFuture(signer))
.map(future -> future.thenCombine(CompletableFuture.supplyAsync(() -> signatureService.getSigningUrl(future.join().getSignerId())),
(signer, url) -> {
signer.setUrl(url);
return url;
}
));
CompletableFuture<String> [] futureArr = streamFutures.toArray(size -> new CompletableFuture[size]);
List<CompletableFuture<String>> futures = Arrays.asList(futureArr);
CompletableFuture<Void> allFuturesVoid = CompletableFuture.allOf(futureArr);
allFuturesVoid.join();
CompletableFuture<List<?>> allCompletableFuture = allFuturesVoid.thenApply(future -> futures.stream().map(completableFuture -> completableFuture.join()).collect(Collectors.toList()));
There is good tutorial here https://m-hewedy.blogspot.com/2017/02/completablefutureallof-that-doenst.html and here https://medium.com/#senanayake.kalpa/fantastic-completablefuture-allof-and-how-to-handle-errors-27e8a97144a0.

Set S3 bucket dynamically using S3 Message Handler and Spring Integration

How can I pass in an 'asset' POJO to the S3MessageHandler using Spring Integration and change the bucket?
I would like to be able to change the bucket, based on a folder in the asset, to something like 'root-bucket/a/b/c.'
Each asset can potentially have a different sub-path.
#Bean
IntegrationFlow flowUploadAssetToS3()
{
return flow -> flow
.channel(this.channelUploadAsset)
.channel(c -> c.queue(10))
.publishSubscribeChannel(c -> c
.subscribe(s -> s
// Publish the asset?
.<File>handle((p, h) -> this.publishS3Asset(
p,
(Asset)h.get("asset")))
// Set the action
.enrichHeaders(e -> e
.header("s3Command", Command.UPLOAD.name()))
// Upload the file
.handle(this.s3MessageHandler())));
}
MessageHandler s3MessageHandler()
{
return new S3MessageHandler(amazonS3, "root-bucket");
}
Thanks to the answer below, I got this working like so:
final String bucket = "'my-root";
final Expression bucketExpression = PARSER.parseExpression(bucket);
final Expression keyExpression = PARSER.parseExpression("headers['asset'].bucket");
final S3MessageHandler handler = new S3MessageHandler(amazonS3, bucketExpression);
handler.setKeyExpression(keyExpression);
return handler;
There is bucketExpression option which is SpEL to let to resolve a bucket from the requestMessage:
private static SpelExpressionParser PARSER = new SpelExpressionParser();
MessageHandler s3MessageHandler() {
Expression bucketExpression =
PARSER.parseExpression("payload.bucketProperty");
return new S3MessageHandler(amazonS3, bucketExpression);
}
Also be aware that root-bucket/a/b/c. isn't bucket name. You should consider to distinguish to bucket and the key to build that complex sub-folder path. For that purpose there is keyExpression option with similar functionality to resolve the key in bucket against requestMessage.

How to create SubCommunities using the Social Business Toolkit Java API?

In the SDK Javadoc, the Community class does not have a "setParentCommunity" method but the CommunityList class does have a getSubCommunities method so there must be a programmatic way to set a parent Community's Uuid on new Community creation. The REST API mentions a "rel="http://www.ibm.com/xmlns/prod/sn/parentcommunity" element". While looking for clues I check an existing Subcommunity's XmlDataHandler's nodes and found a link element. I tried getting the XmlDataHandler for a newly-created Community and adding a link node with href, rel and type nodes similar to those in the existing Community but when trying to update or re-save the Community I got a bad request error. Actually even when I tried calling dataHandler.setData(n) where n was set as Node n=dataHandler.getData(); without any changes, then calling updateCommunity or save I got the same error, so it appears that manipulating the dataHandler XML is not valid.
What is the recommended way to specify a parent Community when creating a new Community so that it is created as a SubCommunity ?
The correct way to create a sub-community programatically is to modify the POST request body for community creation - here is the link to the Connections 45 infocenter - http://www-10.lotus.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.5+API+Documentation#action=openDocument&res_title=Creating_subcommunities_programmatically_ic45&content=pdcontent
We do not have support in the SBT SDK to do this using CommunityService APIs. We need to use low level Java APIs using Endpoint and ClientService classes to directly call the REST APIs with the appropriate request body.
I'd go ahead and extend the class CommunityService
then go ahead and add CommunityService
https://github.com/OpenNTF/SocialSDK/blob/master/src/eclipse/plugins/com.ibm.sbt.core/src/com/ibm/sbt/services/client/connections/communities/CommunityService.java
Line 605
public String createCommunity(Community community) throws CommunityServiceException {
if (null == community){
throw new CommunityServiceException(null, Messages.NullCommunityObjectException);
}
try {
Object communityPayload;
try {
communityPayload = community.constructCreateRequestBody();
} catch (TransformerException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityPayloadException);
}
String communityPostUrl = resolveCommunityUrl(CommunityEntity.COMMUNITIES.getCommunityEntityType(),CommunityType.MY.getCommunityType());
Response requestData = createData(communityPostUrl, null, communityPayload,ClientService.FORMAT_CONNECTIONS_OUTPUT);
community.clearFieldsMap();
return extractCommunityIdFromHeaders(requestData);
} catch (ClientServicesException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityException);
} catch (IOException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityException);
}
}
You'll want to change your communityPostUrl to match...
https://greenhouse.lotus.com/communities/service/atom/community/subcommunities?communityUuid=2fba29fd-adfa-4d28-98cc-05cab12a7c43
and where the Uuid here is the parent uuid.
I followed #PaulBastide 's recommendation and created a SubCommunityService class, currently only containing a method for creation. It wraps the CommunityService rather than subclassing it, since I found that preferrable. Here's the code in case you want to reuse it:
public class SubCommunityService {
private final CommunityService communityService;
public SubCommunityService(CommunityService communityService) {
this.communityService = communityService;
}
public Community createCommunity(Community community, String superCommunityId) throws ClientServicesException {
Object constructCreateRequestBody = community.constructCreateRequestBody();
ClientService clientService = communityService.getEndpoint().getClientService();
String entityType = CommunityEntity.COMMUNITY.getCommunityEntityType();
Map<String, String> params = new HashMap<>();
params.put("communityUuid", superCommunityId);
String postUrl = communityService.resolveCommunityUrl(entityType,
CommunityType.SUBCOMMUNITIES.getCommunityType(), params);
String newCommunityUrl = (String) clientService.post(postUrl, null, constructCreateRequestBody,
ClientService.FORMAT_CONNECTIONS_OUTPUT);
String communityId = newCommunityUrl.substring(newCommunityUrl.indexOf("communityUuid=")
+ "communityUuid=".length());
community.setCommunityUuid(communityId);
return community;
}
}

Resources