I'm new to reactive programming
I want to save the object to the DB when handling an error, but as far as I know, calling the block() method is not the best practice
mailFailureRepository.save(failure).block();
Is there a way to do this without interrupting the flow?
I think I should rebuild the thread chain, but I don't quite understand how to do it in my case
I will also be glad of any information resources
public Mono<Object> sendEmail(SendEmailRequest request)
throws MailTemplateNotSupportedException, ExactTargetException {
log.debug("Send email process started.");
return validateRequestAndSendMail(request)
.onErrorResume(error -> {
if (error instanceof ExactTargetException ex) {
MailFailure failure = MailFailure.builder()
.templateKey(request.getTemplateKey())
.templateParams(request.getTemplateParams() != null ? request.getTemplateParams().toString() : null)
.subscriberHash(request.getSubscriberHash())
.email(request.getEmail())
.responseStatus(ex.getStatus())
.responsePayload(ex.getBody())
.build();
mailFailureRepository.save(failure).block();
return Mono.error(error);
}
return Mono.error(error);
});
Why not save and then return error without blocking:
public Mono <Object> sendEmail(SendEmailRequest request)
throws MailTemplateNotSupportedException, ExactTargetException {
log.debug("Send email process started.");
return validateRequestAndSendMail(request)
.onErrorResume(error -> {
Mono<Object> errorMono = Mono.error(error);
if (error instanceof ExactTargetException ex) {
return mailFailureRepository.save(buildFailureEmail(request))
.thenReturn(errorMono);
}
return errorMono;
});
}
private MailFailure buildFailureEmail(SendEmailRequest request) {
return MailFailure.builder()
.templateKey(request.getTemplateKey())
.templateParams(request.getTemplateParams() != null ? request.getTemplateParams().toString() : null)
.subscriberHash(request.getSubscriberHash())
.email(request.getEmail())
.responseStatus(ex.getStatus())
.responsePayload(ex.getBody())
.build();
}
Related
I am using Vert.x in my project, I used future() to get the results from a MongoDB query. However when I do future().result it returns "null". I want the result to be saved in the future and I will use it for other APIs. Is there any guide for me, I will be very grateful and appreciate if someone give me a solution. Thanks
router.class
rivate void getClazzById(RoutingContext rc) {
Future<JsonObject> future = Future.future();
String clazzId = rc.request().getParam("clazzId");
classService.getClazzById(clazzId, res -> {
System.out.println(res.result());
if (res.succeeded()) {
JsonObject result = res.result();
if (result != null) {
future.complete(res.result());
rc.response().setStatusCode(200).putHeader("Content-Type", "application/json; charset=utf-8")
.end(result.encodePrettily());
} else {
rc.response().setStatusCode(400).putHeader("Content-Type", "application/json; charset=utf-8")
.end(new JsonObject().put("error", "Class not found!").encodePrettily());
}
} else
rc.fail(res.cause());
});
future.setHandler(s -> {
if (s.succeeded()) {
System.out.println("sss: " +s.result()); // print: {"_id":"123", "name":"abc"}
}
else {
System.out.println("fail");
}
});
System.out.println("hhhhhh: " + future.result()); // I want to print {"_id":"123", "name":"abc"}
}
service.class
public void getClazzById(String clazzId, Handler<AsyncResult<JsonObject>> resultHandler) {
JsonObject query = new JsonObject().put("_id", clazzId);
client.findOne(Collection.CLAZZ, query, null, ar -> {
if (ar.succeeded()) {
if (ar.succeeded()) {
JsonObject result = ar.result();
if (result == null) {
resultHandler.handle(Future.failedFuture("Error"));
} else {
resultHandler.handle(Future.succeededFuture(result));
}
} else {
ar.cause();
}
}
});
}
When writing asynchronous code, you are carried to use the framework / runtime semantics and tools for communication.
You are already leveraging one of Vert.x's way of async communication, the Future - but in the wrong manner trying to retrieve its result inline.
Instead of having the Future result accessed within your method, you need to return it as your mean of communication to a caller, which would be able to set a completion handler (Handler) to it:
private Future<JsonObject> getClazzById(RoutingContext rc) {
Future<JsonObject> future = Future.future();
String clazzId = rc.request().getParam("clazzId");
classService.getClazzById(clazzId, res -> {
if (res.succeeded()) {
JsonObject result = res.result();
if (result != null) {
future.complete(res.result()); // set the retrieved result
rc.response().setStatusCode(200).putHeader("Content-Type", "application/json; charset=utf-8")
.end(result.encodePrettily());
} else {
future.complete(null); // you need to provide 'null' result to avoid caller blocking
rc.response().setStatusCode(400).putHeader("Content-Type", "application/json; charset=utf-8")
.end(new JsonObject().put("error", "Class not found!").encodePrettily());
}
} else
rc.fail(res.cause());
});
return future; // return the future to caller
}
An interested caller would be able to set a handler for Future completion as needed:
getClassById(rc).setHandler(s -> {
if (s.succeeded()) {
System.out.println("sss: " +s.result()); // print: {"_id":"123", "name":"abc"}
}
else {
System.out.println("fail");
}
});
As a side note: you are not setting the API boundaries properly in your business logic as you are trying to resolve the HTTP Response result which generally denotes the request processing end while still returning the query result to be handled in some other manner.
I am using vert.x and I am trying to list my DynamoDB tables. Here is how I build the DynamoDB client.
private static DynamoDbAsyncClient buildDynamoDBAsyncClient(final Vertx vertx) {
return VertxSdkClient.withVertx(DynamoDbAsyncClient.builder(), vertx.getOrCreateContext())
.build();
}
And here is the request I make
CompletableFuture<ListTablesResponse> response = client.listTables(ListTablesRequest.builder()
.build());
// Map the response to another CompletableFuture containing just the table names
CompletableFuture<List<String>> tableNames = response.thenApply(ListTablesResponse::tableNames);
// When future is complete (either successfully or in error) handle the response
tableNames.whenComplete((tables, err) -> {
if (tables != null) {
tables.forEach(System.out::println);
} else {
// Handle error
err.printStackTrace();
}
client.close();
});
tableNames.join();
I get warnings of blocked threads and then a request timeout. What am I doing wrong? Thank you in advance.
As Tim stated in the comment, tablesName.join() blocks the thread, you should let the CompletableFuture handle async.
Try this:
private Future<ListTablesResponse> listTables() {
Promise<ListTablesResponse> promise = Promise.promise();
CompletableFuture<ListTablesResponse> response = client.listTables();
response.handleAsync((result, error) -> {
if (error == null) {
promise.complete(result);
} else {
promise.fail(error);
}
return null;
});
return promise.future();
}
then in your vertx request handler:
this.listTables()
.onSuccess(listTablesResponse -> {...})
.onFailure(throwable -> {...});
My requirement is as follows.
Get ApptReq object which will have apptId. Get Appt object from DB and update Appt object with the data from ApptReq and update the table.
Mono<User> monoUser = retrieveUser();
public Mono<ServerResponse> updateAppt(ServerRequest request) {
return apptRepository.findById(request.bodyToMono(ApptReq.class).map(ApptReq::getApptId)).flatMap(appt -> {
return updateAppt(appt, request.bodyToMono(ApptReq.class)).flatMap(apptRepository::save).flatMap(
res -> ServerResponse.created(URI.create(String.format(APPT_URI_FORMAT, res.getApptId()))).build());
});
}
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.map(req -> {
appt.setNotes(req.getNotes());
return monoUser.map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Here getting error in updateAppt method that
can not convert from Mono<Object> to Mono<Appt>.
Is there any better approach?
You've got it almost. I changed nothing in your updateAppt(ServerRequest request) method but made just a slight adjustment in your updateAppt(Appt appt, Mono<ApptReq> apptReq) method, as follows:
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.flatMap(req -> {
appt.setNotes(req.getNotes());
return retrieveUser().map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Watch out for the apptReq.flatMap instead of your apptReq.map and everything works fine. Give it a try!
Reminder: Be careful with nested Monos in other Monos or more generally said nested Publishers.
I am trying to return a CompletableFuture that will return a response from Amazon. My code first checks to see if a response is cached locally. If so, it returns the response, otherwise it calls Amazon. [Note: The real version will also cache the response received from Amazon, but I haven't included that as the code is already pretty complicated.]
Is there a way to change the implementation of my callAmazon method (or otherwise reorganize the code) so that I don't have to "manually" copy the response state from amazonApi to the finalResponse?
I don't want to return cacheCheck directly because I don't want the caller to be able to complete() it.
public CompletableFuture<Response> fetchProductList() {
CompletableFuture<Response> finalResponse = new CompletableFuture<>();
CompletableFuture<Response> cacheCheck = //...
// First, see if we have a cached copy
cacheCheck.whenComplete((response, throwable) -> {
if (throwable == null) {
// Cache hit. Return the cached response
finalResponse.complete(response);
} else {
// Cache miss. Call Amazon
callAmazon(finalResponse);
}
});
return finalResponse;
}
private void callAmazon(CompletableFuture<Response> finalResponse) {
CompletableFuture<Response> amazonApi = //...
amazonApi.whenComplete((response, throwable) -> {
// Copy the state to the `finalResponse`
if (throwable == null) {
finalResponse.complete(response);
} else {
finalResponse.completeExceptionally(throwable);
}
});
}
What makes your requirement so complex is the fact that cacheCheck can throw an exception.
What I would do in your case is to refactor the cache to deliver either null if the value was not found in the cache, or the actual Response, if the value was in the cache.
Furthermore, I would modify callAmazon to return directly the CompletableFuture:
private CompletableFuture<Response> callAmazon() {
CompletableFuture<Response> amazonApi = //...
return amazonApi;
}
This way you can use thenCompose:
final CompletableFuture<Response> cacheCheck = //...
final CompletableFuture<Response> amazonResponse = callAmazon();
final CompletableFuture<Response> finalResult =
cachedResponse.thenCompose(cacheResult -> {
return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult);
});
If you really need to throw an exception from the cache, you can use exceptionally to convert the exception to a null value, and then use thenCompose to decide if you use the cache value, or call Amazon:
final CompletableFuture<Response> finalResult = cachedResponse.exceptionally(e -> {
return null;
}).thenCompose(cacheResult -> {
return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult);
});
I would like to annotate some of my test cases with KnownFault - which would do pretty much what expectedException does plus some magic using YouTrack's REST API. I would also like to have an IntermittentFailure attribute which would mean that I'm aware that the test might occasionally fail with [exception] [message] but I wouldn't want this to block the rest of my build chain.
After some research I found that my test class should implement IHookable, then I could have something like this:
#Override
public void run(IHookCallBack callBack, ITestResult result) {
callBack.runTestMethod(result);
if (result.getThrowable().getCause() instanceof IllegalArgumentException){
System.out.println("This is expected.");
result.setThrowable(null);
}
else{
System.out.println("Unexpected exception");
}
}
The problem with this is the actual implementation of invokeHookable:
final Throwable[] error = new Throwable[1];
IHookCallBack callback = new IHookCallBack() {
#Override
public void runTestMethod(ITestResult tr) {
try {
invokeMethod(thisMethod, testInstance, parameters);
} catch (Throwable t) {
error[0] = t;
tr.setThrowable(t); // make Throwable available to IHookable
}
}
#Override
public Object[] getParameters() {
return parameters;
}
};
hookable.run(callback, testResult);
if (error[0] != null) {
throw error[0];
}
Unfortunately that last line means that my test case is going to throw an exception no matter what as the error array is completely out of my hands in the run method.
So, what would be the proper way of intercepting an exception and handling it the way I want to?
What you are trying to do is really interesting. You should try to propose changes on https://github.com/cbeust/testng/pull/
But maybe IHookable is not the best listener you can use. Did you try IInvokedMethodListener?
void afterInvocation(IInvokedMethod method, ITestResult result) {
if (result.getThrowable().getCause() instanceof IllegalArgumentException) {
System.out.println("This is expected.");
result.setThrowable(null);
result.setStatus(SUCCESS); // If you want to change the status
} else {
System.out.println("Unexpected exception");
}
}