return Mono.error inside Map Spring Webflux - spring

For Matching condition, return Mono.error from code , but gives compilation error. I commented out
//return Mono.error(new RuntimeException("User Phone Exists already"));
Result:
Compilation Error: Required Type Mono<EventSlotBook>, Provided
Mono<Object>
Code:
public Mono<EventSlotBook> getEventSlotBookWithAppointmentId(EventSlotBook eventSlotBookEntity) {
Query query = new Query();
query.addCriteria(
new Criteria().andOperator(
Criteria.where("eventId").is(eventSlotBookEntity.getEventId()),
Criteria.where("eventConfigId").is(eventSlotBookEntity.getEventConfigId()),
Criteria.where("eventSlotId").is(eventSlotBookEntity.getEventSlotId())));
return this.reactiveMongoTemplate.findOne(query, EventSlotBook.class)
.map(eventSlotBookEntityFromDb -> {
EventSlotBook eventSlotNewEntity = eventSlotBookEntityFromDb.toBuilder().build();
if(eventSlotNewEntity.getEventUsers() != null) {
for(EventUser eventUserIno:eventSlotNewEntity.getEventUsers()) {
if(eventUserIno.getPhoneNumber().equalsIgnoreCase(eventSlotBookEntity.getEventUser().getPhoneNumber())){
//return Mono.error(new RuntimeException("User Phone Exists already"));
}
}
}
int maxTokenVal = Integer.valueOf(eventSlotNewEntity.getMaxTokenVal()).intValue() + 1;
EventUser eventUser = new EventUser(eventSlotNewEntity.getEventUser().getName(),eventSlotNewEntity.getEventUser().getPhoneNumber(),String.valueOf(maxTokenVal));
eventSlotNewEntity.getEventUsers().add(eventUser);
eventSlotNewEntity.setMaxTokenVal(String.valueOf(maxTokenVal));
eventSlotNewEntity.setEventUser(eventUser);
return eventSlotNewEntity;
//return Mono.error(new RuntimeException("Ts"));
})
.switchIfEmpty(getEventSlotBook(eventSlotBookEntity));
}
caller of Method : I should handle Mono.error and return to rest API that user already exists ?. Please help on this
public Mono<EventSlotBookRequestDto> saveEventSlotBook(Mono<EventSlotBookRequestDto> eventSlotBookRequestDtoMono){
log.info("Start::SaveEventSlotBook");
Mono<EventSlotBookRequestDto> eventDtoSaved =
eventSlotBookRequestDtoMono.map(AppUtils::dtoToEntity)
.flatMap(eventSlotEntity -> getEventSlotBookWithAppointmentId(eventSlotEntity))
.doOnNext(eventSlotEntityBeforeSave -> {
log.info("####BeforeSave::{}",eventSlotEntityBeforeSave);
})
.flatMap(eventSlotBookrepository::save)
.doOnNext( eventSlotBookAfterSave -> {
log.info("####AfterSave::{}",eventSlotBookAfterSave);
})
.map(AppUtils::entityToDto);
log.info("End::SaveEventSlotBook");
return eventDtoSaved;
}

map is used to apply a synchronous function to each item, therefore you can't return Mono from it. To return an error from map you could just throw an exception and error signal will be emited. As an alternative you you use handle operator and use SynchronousSink to emit next or error signal.
But in your case you need to use flatMap instead because saveEventSlotBook returns Mono and should be transformed asynchronously.

Related

Mono<Object> being returned instead of Mono<ResponseEntity> when mapping (Java 8)

Trying to practice reactive coding for an API but I'm struggling to understand what I'm doing wrong when using flatMap() and map() to cast to a ResponseEntity object. The error mentions that the code is returning a Mono<Object> and cant be cast/transformed into a Mono<ResponseEntity<>>.
Public Mono<ResponseEntity<?> deleteEndpoint(String someId) {
return db.existsById(someId).flatMap(exists -> {
if (!exists) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
else {
Mono<Boolean> deletedStuff1 = someFunct();
Mono<Boolean> deletedStuff2 = anotherFunct();
Mono<Tuple2<Boolean, Boolean>> deletedStuff = Mono.zip(deletedStuff1, deletedStuff2);
return deletedStuff.then(Mono.just(ResponseEntity.status(NO_CONTENT).build());
}
});
}
All help is appreciated
From .flatMap() you must return Publisher, not actual object
In this if statement you return ResponseEntity instead of Mono<ResponseEntity>
So, just wrap it with Mono
if (!exists) {
return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).build());
} else {
// ...

How to define dependencies on two client calls in quarkus reactive programming

I have two Client APIs that return an Uni.
Uni<Customer> getCustomer(customerID)
Uni<Address> getAddress(addressID)
And I want to open a REST API
Uni<FullCustomer> getFullCustomer(String customerID)
The logic is to make the Customer Client call first. If the returned customer object has addressID then make the second Address Client call and get shipping address details. If shipping address is not available then just wrap the customer in FullCustomer object and return else wrap both customer and address in FullCustomer object and return.
I dont want to block the thread on client call (await().indefinitely()), hence i am using onItem and transfer method call. But my code returns a Uni<Uni> and i want it to return a Uni.
#GET
#Path("api/customer/{id}")
#Produces({ "application/json" })
Uni<Uni<FullCustomer>> getFullCustomer(#PathParam("id") String customerID){
Uni<Customer> customerResponse = getCustomer(customerID);
Uni<Uni<FullCustomer>> asyncResponse = customerResponse.onItem().transform(customer -> {
if (customer.getAddressId() != null) {
Uni<Address> addressResponse = getAddress(customer.getAddressId());
Uni<FullCustomer> fullCustomer = addressResponse.onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
}
return Uni.createFrom().item(new FullCustomer(customer));
});
return asyncResponse;
}
How can I rewrite my code so that it returns Uni keeping reactive ( async client ) calls
Got the solution. Thanks Ladicek for comments.
public Uni<FullCustomer> getFullCustomer(#PathParam("id") String customerID) {
return getCustomer(customerID)
.onItem()
.transformToUni(customer -> {
if (customer.getAddressId() != null) {
return getAddress(customer.getAddressId()).onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
} else {
return Uni.createFrom().item(new FullCustomer(customer));
}
});
}

Getting multiple Mono objects with reactive Mongo queries

I'm using the webflux framework for spring boot, the behavior I'm trying to implement is creating a new customer in the database, if it does not already exist (throw an exception if it does)
and also maintain another country code database (if the new customer is from a new country, add to the database, if the country is already saved, use the old information)
This is the function in the service :
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
Mono<Customer> customerFromDB = customerDB.findByEmail(customer.getEmail());
Mono<Country> countryFromDB = countryDB.findByCountryCode(customer.getCountryCode());
Mono<Customer> c = customerFromDB.zipWith(countryFromDB).doOnSuccess(new Consumer<Tuple2<Customer, Country>>() {
#Override
public void accept(Tuple2<Customer, Country> t) {
System.err.println("tuple " + t);
if(t == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
customerDB.save(customer).subscribe();
return;
}
Customer cus = t.getT1();
Country country = t.getT2();
if(cus != null) {
throw new CustomerAlreadyExistsException();
}
if(country == null) {
countryDB.save(new Country(customer.getCountryCode(), customer.getCountryName())).subscribe();
}
else {
customer.setCountryName(country.getCountryName());
}
customerDB.save(customer).subscribe();
}
}).thenReturn(customer);
return c;
}
My problem is, the tuple returns null if either country or customer are not found, while I need to know about them separately if they exist or not, so that I can save to the database correctly.
country == null is never true
I also tried to use customerFromDB.block() to get the actual value but I receive an error that it's not supported, so I guess that's not the way
Is there anyway to do two queries to get their values?
Solved it with the following solution:
public Mono<Customer> createNewCustomer(Customer customer) {
if(!customer.isValid()) {
return Mono.error(new BadRequestException("Bad email or birthdate format"));
}
return customerDB.findByEmail(customer.getEmail())
.defaultIfEmpty(new Customer("empty", "", "", "", "", ""))
.flatMap(cu -> {
if(!cu.getEmail().equals("empty")) {
return Mono.error(new CustomerAlreadyExistsException());
}
return countryDB.findByCountryCode(customer.getCountryCode())
.defaultIfEmpty(new Country(customer.getCountryCode(), customer.getCountryName()))
.flatMap(country -> {
customer.setCountryName(country.getCountryName());
customerDB.save(customer).subscribe();
countryDB.save(country).subscribe();
return Mono.just(customer);});
});
}
Instead of doing both queries simulatneaously, I queried for one result and then queries for the next, I think this is the reactive way of doing it, but I'm open for corrections.

Error in forEach when use a consumer to avoid try-catch in forEach in Java8

I have a log() method to avoid try catch statement in forEach() below which was working in other code.
public <T> Consumer<T> log(LogConsumer<T, Throwable> logConsumer)
{
return i -> {
try
{
logConsumer.accept(i);
}
catch (Throwable e)
{
log("e = " + e);
}
};
}
#FunctionalInterface
public interface LogConsumer<T, E extends Throwable> {
void accept(T t) throws E;
}
Now I just want to use log in forEach below but I have the red rippled line in LINE such that
new Task.runJob(job, type))
I have red rippled line under job, type in
"runJob(Job, JobType) in Task cannot be applied to (java.lang.Object, < lambda parameter>)"
Now sure how to fix it to use log in forEach just to avoid
try-catch inside of it.
execute() {
Map<Job, JobType> map = getJobMap();
map.forEach( log((job, type)-> new Taks().runJob(job,type)) ); // LINE: error here
}
class Task {
public String runJob(Job job, JobType type) throws Exception
{
...
return result;
}
}
It happens because you cannot execute functions that throw exceptions using lambda expressions. You have to handle the exception using try-catch block. However, in order for your code to look more readable, create a function, that will handle the exception and return the desired result.
class Task {
public String runJob(Job job, JobType type)
{
try {
...
return result;
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
}
In case if you care what will be the result, map it and filter for the result of your function is not null, otherwise, ignore it, but watch logs for any errors.
And then call it like shown below.
Notice: both ways work below, but the second way is more robust because you can handle the scenario when not all jobs were executed without exception.
execute() {
Map<Job, JobType> map = getJobMap();
// First way
map.forEach( log((job, type) -> new Taks().runJob(job,type)) );
// Another way
List<Object> batchResult = map.entrySet().stream()
.map((job, type) -> new Task().runJob(jon, type))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (batchResult.size() == map.size()) {
// everythings is ok (all operations resulted in non-null result
} else {
// Have to study logs and figure out what went wrong
}
}

how to return an exception from async in asp.net webapi

I have a WebAPI2 mvc app where I'm doing Get/Post to another api. My code looks like below
public Task<SomeEntity> AddAsync(SomeEntity someEntity)
{
try
{
var response = apiService.PostItem(url, someEntity);
if (response == null || response!="Successful")
{
throw new InvalidOperationException(response);
}
}
catch (Exception ex)
{
_logger.Error("Error " + ex.Message);
// how to return this error or exception;
}
return Task.FromResult(someEntity);
}
If the call to the internal api return an exception string then I need to forward it from this method call. Any ideas how can I do it? thanks
You can use IHttpActionResult or HttpResponseMessage as your return type and return proper HTTP-Status codes. In case of exception you can return [if you return IHttpActionResult ]
public IHttpActionResult Error()
{
var error = new HttpError();
return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.BadRequest, error));
}
or as in your example, throw HttpResponseException with appropriate status code.
public Product GetProduct(int id)
{
Product item = repository.Get(id);
if (item == null)
{
var message = string.Format("Product with id = {0} not found", id);
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
}
else
{
return item;
}
}

Resources