How to use Mono<Boolean> in if else conditional statement? - java

I am using Flux<Document> in reactive, so as to make my Rest Service reactive. I am returning ResponseEntity<Flux<Document>> as response to my rest service. Right now my service is always returning HttpStatus.ok(), but I want to enhance it to return HttpStatus.noContent() in case of no content is found.
To achieve this am trying to check the size of Flux. I figured out that this can be achieved either by .count() or .hasElements().
IF I consider .hasElements() then it returns Mono<Boolean>.
I am trying to understand as a newbie that how can I use this in making decisions between HttpStatus.ok() and HttpStatus.noContent().
Also is this the right way to use conditional statements in reactive or is there any other way to achieve it.
Request you to please help.

So here is what I did to accomplish the above ask:
final Flux<Document> returnDoc = <Reference of what I received from the Service layer>;
return returnDoc.hasElements()
.map(isNotEmpty -> {
if (isNotEmpty)
return ResponseEntity.ok().body(returnDoc);
else {
return ResponseEntity.noContent().build();
}
});
This worked for me. Let me know if there is any other solution which is better.

The trick with reactive conditionals is to utilize a couple of operators to achieve the if / else behaviour.
Some operators I learnt from using Webflux over the years:
filter(): filters the stream by some predicate (e.g. if (2 > 1))
map(): maps the response to some type if filter() emits true
flatMap(): maps the response to a Publisher (i.e. Mono/Flux)
switchIfEmpty(): emits a default Publisher if filter() emits false
defaultIfEmpty() emits a default type
I will share my redisson cache & r2dbc code as an example.
Here is our scenario in pseudocode:
If a key is found in cache
return value
Else
call database
set key & value pair in cache
return value
In either case, the value is wrapped into a ResponseEntity distinguished by status and the body.
#Override
public Mono<ResponseEntity<Res<UserSettings>>> getUserSetting(String username) {
Mono<UserSettings> queryAndSet =
userSettingsRepository
.findByUsername(username)
.flatMap(v1 -> cacheRepository.set("user_settings", username, v1).thenReturn(v1));
return cacheRepository
.get("user_settings", username, new TypeReference<UserSettings>() {}) // if then
.switchIfEmpty(queryAndSet) // else
.map(ResponseUtil::success) // if then
.defaultIfEmpty(singleError(UserMsg.USER_SETTINGS_NOT_FOUND.getCode())) // else
.map(v1 -> ResponseEntity.status(elseNotFound(v1)).body(v1)); // finally
}
CacheRepository interface specs:
public interface CacheRepository {
Mono<Void> set(String table, String key, Object value);
Mono<Void> set(String table, String key, Object value, Long ttl, TimeUnit timeUnit);
<T> Mono<T> get(String table, String key, TypeReference<T> type);
Mono<Boolean> delete(String table, String key);
}
ResponseUtil that helps with ResponseEntity wrapper:
public class ResponseUtil {
private ResponseUtil() {}
public static <T> Response<T> success(T data) {
return Response.<T>builder().success(true).data(data).build();
}
public static <T> Response<T> singleError(String error) {
return Response.<T>builder().success(false).errors(List.of(error)).build();
}
public static <T> Response<T> multipleErrors(List<String> errors) {
return Response.<T>builder().success(false).errors(errors).build();
}
public static HttpStatus elseBadRequest(Response<?> res) {
return Boolean.TRUE.equals(res.isSuccess()) ? HttpStatus.OK : HttpStatus.BAD_REQUEST;
}
public static HttpStatus elseNotFound(Response<?> res) {
return Boolean.TRUE.equals(res.isSuccess()) ? HttpStatus.OK : HttpStatus.NOT_FOUND;
}
}
And the Response:
// Idiotic wrapper
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Response<T> {
private T data;
private boolean success;
private List<String> errors;
}

Related

Is it possible to design if statement branching in java in a better way?

Currently, some of our query-related APIs and Controllers are:
[GET] api/study-group?sortby=latest
[GET] api/study-group?sortby=star
[GET] api/study-group?sortby=level
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/study-group")
public class StudyGroupController {
private final StudyGroupService studyGroupService;
#GetMapping
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroup(
#RequestParam(name = "sortby", defaultValue = "createdat") String sortBy) {
studyGroupService.findAll(sortBy);
......
}
}
It handles the logic for the controller, but I wonder what kind of method would be better for branching processing for the sorting method.
The first method is branch processing through the if statement.
#Service
#RequiredArgsConstructor
public class StudyGroupService {
public List<StudyGroupResponseDTO> findAll(String sortBy) {
if(sortBy.equals("star") {
searchStudyOrderByStar()l
}else if(sortBy.equals("...")) {
....
}else {
...
}
}
private List<StudyGroupResponseDTO> sort(Function<StudyGroup, Comparable> function) {
return studyGroupRepository
.findAll()
.stream()
.sorted(Comparator.comparing(function, Comparator.reverseOrder())
.thenComparing(StudyGroup::getSeason, Comparator.reverseOrder()))
.map(studyGroupMapper::toResponseDTO)
.collect(Collectors.toList());
}
private List<StudyGroupResponseDTO> searchStudyOrderByCreatedAt() {
return sort(studyJournalService::searchLatestJournalCreatedAt);
}
private List<StudyGroupResponseDTO> searchStudyOrderByStar()
return sort(studyGroup -> studyGroup.getJournals().size());
}
private List<StudyGroupResponseDTO> searchStudyOrderByLike() {
return sort(StudyGroup::getLike);
}
}
Of course, I'm also thinking of using Enum instead of String. However, even if it is changed, setting a branch with an if statement does not seem to change.
Another way to think about it is to use Map.
#Service
#RequiredArgsConstructor
public class StudyGroupService {
private final Map<String, Function<StudyGroup, Comparable> sortMap;
public List<StudyGroupResponseDTO> findAll(String sortBy) {
sortMap.get(sortBy);
....
}
}
Which method do you think looks best?
Or is there a better way than the ones listed above??
If you have any good comments, we would appreciate your feedback!
You can use Spring's GetMapping.params() to specify mapping to be invoked for certain request parameter value to get rid of if-else construct entirely.
It's an alias for RequestMapping.params().
The parameters of the mapped request, narrowing the primary mapping.
Same format for any environment: a sequence of "myParam=myValue" style expressions, with a request only mapped if each such parameter is found to have the given value.
Then you would need to define additional mapping for each possible value of the parameter, spring will take care of invoking correct mapping, depending on the value of sortby.
#GetMapping(params = "sortby=latest")
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroupSortByLatest() {
//logic for sort by latest
}
#GetMapping(params = "sortby=star")
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroupSortByStar() {
//logic for sort by star
}

Define what method to call based on value of Optional

Is it possible, to somehow alter the code below, so in case of no status value provided to return all the PaymentLog objects stored in the database instead of only the ones with status equal with 404? Basically, I would like if the status variable has not been provided to call another method in the service layer logService.getAllPaymentLogs()
#GetMapping(Endpoints.LOGS.PAYMENT_LOGS)
public Page<PaymentLog> getPaymentLog(#RequestParam Optional<Integer> status) {
return logService.getPaymentLogStatus(status.orElse(404), PageRequest.of(0, 10));
}
These are the getPaymentLogStatus() and getAllPaymentLogs
#Override
public Page<PaymentLog> getPaymentLog(Pageable pageable) {
return paymentLogRepository.getAllBy(pageable);
}
And
#Override
public Page<PaymentLog> getPaymentLog(int status, Pageable pageable) {
return paymentLogRepository.getAllByStatus(status, pageable);
}
#123 answered the question in the commenting session:
status.map(s -> getPaymentLog(s, page)).orElseGet(() -> getPaymentLog(page))

Spring WebFlux - how to get data from DB to use in the next step

I use Spring WebFlux (Project Reactor) and I'm facing the following problem:
I have to get some data from db to use them to call another service - everything in one stream. How to do that?
public Mono<MyObj> saveObj(Mono<MyObj> obj) {
return obj
.flatMap(
ob->
Mono.zip(
repo1.save(
...),
repo2
.saveAll(...)
.collectList(),
repo3
.saveAll(...)
.collectList())
.map(this::createSpecificObject))
.doOnNext(item-> createObjAndCallAnotherService(item));
}
private void createObjAndCallAnotherService(Prot prot){
myRepository
.findById(
prot.getDomCred().stream()
.filter(Objects::nonNull)
.findFirst()
.map(ConfDomCred::getCredId)
.orElse(UUID.fromString("00000000-0000-0000-0000-000000000000")))
.doOnNext( //one value is returned from myRepository -> Flux<MyObjectWithNeededData>
confCred-> {//from this point the code is unreachable!!! - why????
Optional<ConfDomCred> confDomCred=
prot.getDomCreds().stream().filter(Objects::nonNull).findFirst();
confDomCred.ifPresent(
domCred -> {
ProtComDto com=
ProtComDto.builder()
.userName(confCred.getUsername())
.password(confCred.getPassword())
.build();
clientApiToAnotherService.callEndpintInAnotherService(com); //this is a client like Feign that invokes method in another service
});
});
}
UPDATE
When I invoke
Flux<MyObj> myFlux = myRepository
.findById(
prot.getDomCred().stream()
.filter(Objects::nonNull)
.findFirst()
.map(ConfDomCred::getCredId)
.orElse(UUID.fromString("00000000-0000-0000-0000-000000000000")));
myFlux.subscribe(e -> e.getPassword())
then the value is printed
UPDATE2
So as a recap - I think the code below is asynchronous/non-blocking - am I right?
In my
ProtectionCommandService
I had to use subscribe() twice - only then I can call my other service and store them my object: commandControllerApi.createNewCommand
public Mono<Protection> saveProtection(Mono<Protection> newProtection) {
return newProtection.flatMap(
protection ->
Mono.zip(
protectorRepository.save(//some code),
domainCredentialRepository
.saveAll(//some code)
.collectList(),
protectionSetRepository
.saveAll(//some code)
.collectList())
.map(this::createNewObjectWrapper)
.doOnNext(protectionCommandService::createProtectionCommand));
}
ProtectionCommandService class:
public class ProtectionCommandService {
private final ProtectionCommandStrategyFactory protectionCommandFactory;
private final CommandControllerApi commandControllerApi;
public Mono<ProtectionObjectsWrapper> createProtectionCommand(
ProtectionObjectsWrapper protection) {
ProductType productType = protection.getProtector().getProductType();
Optional<ProtectionCommandFactory> commandFactory = protectionCommandFactory.get(productType);
commandFactory
.get()
.createCommandFromProtection(protection)
.subscribe(command -> commandControllerApi.createNewCommand(command).subscribe());
return Mono.just(protection);
}
}
And one of 2 factories:
#Component
#AllArgsConstructor
#Slf4j
public class VmWareProtectionCommandFactory implements ProtectionCommandFactory {
private static final Map<ProductType, CommandTypeEnum> productTypeToCommandType =
ImmutableMap.of(...//some values);
private final ConfigurationCredentialRepository configurationCredentialRepository;
#Override
public Mono<CommandDetails> createCommandFromProtection(ProtectionObjectsWrapper protection) {
Optional<DomainCredential> domainCredential =
protection.getDomainCredentials().stream().findFirst();
return configurationCredentialRepository
.findByOwnerAndId(protection.getOwner(), domainCredential.get().getCredentialId())
.map(credential -> createCommand(protection, credential, domainCredential.get()));
}
and createCommand method returns Mono object as a result of this factory.
private Mono<CommandDetails> createCommand(Protection protection
//other parameters) {
CommandDto commandDto =
buildCommandDto(protection, confCredential, domainCredentials);
String commands = JsonUtils.toJson(commandDto);
CommandDetails details = new CommandDetails();
details.setAgentId(protection.getProtector().getAgentId().toString());
details.setCommandType(///some value);
details.setArguments(//some value);
return Mono.just(details);
UPDATE3
My main method that calls everything has been changed a little bit:
public Mono<MyObj> saveObj(Mono<MyObj> obj) {
return obj
.flatMap(
ob->
Mono.zip(
repo1.save(
...),
repo2
.saveAll(...)
.collectList(),
repo3
.saveAll(...)
.collectList())
.map(this::wrapIntoAnotherObject)
.flatMap(protectionCommandService::createProtectionCommand)
.map(this::createMyObj));
Stop breaking the chain
This is a pure function it returns something, and always returns the same something whatever we give it. It has no side effect.
public Mono<Integer> fooBar(int number) {
return Mono.just(number);
}
we can call it and chain on, because it returns something.
foobar(5).flatMap(number -> { ... }).subscribe();
This is a non pure function, we can't chain on, we are breaking the chain. We can't subscribe, and nothing happens until we subscribe.
public void fooBar(int number) {
Mono.just(number)
}
fooBar(5).subscribe(); // compiler error
but i want a void function, i want, i want i want.... wuuaaa wuaaaa
We always need something to be returned so that we can trigger the next part in the chain. How else would the program know when to run the next section? But lets say we want to ignore the return value and just trigger the next part. Well we can then return a Mono<Void>.
public Mono<Void> fooBar(int number) {
System.out.println("Number: " + number);
return Mono.empty();
}
foobar(5).subscribe(); // Will work we have not broken the chain
your example:
private void createObjAndCallAnotherService(Prot prot){
myRepository.findById( ... ) // breaking the chain, no return
}
And some other tips:
Name your objects correctly not MyObj and saveObj, myRepository
Avoid long names createObjAndCallAnotherService
Follow single responsibility createObjAndCallAnotherService this is doing 2 things, hence the name.
Create private functions, or helper functions to make your code more readable don't inline everything.
UPDATE
You are still making the same misstake.
commandFactory // Here you are breaking the chain because you are ignoring the return type
.get()
.createCommandFromProtection(protection)
.subscribe(command -> commandControllerApi.createNewCommand(command)
.subscribe()); // DONT SUBSCRIBE you are not the consumer, the client that initiated the call is the subscriber
return Mono.just(protection);
What you want to do is:
return commandFactory.get()
.createCommandFrom(protection)
.flatMap(command -> commandControllerApi.createNewCommand(command))
.thenReturn(protection);
Stop breaking the chain, and don't subscribe unless your service is the final consumer, or the one initiating a call.

Returning different generic type with Optional.map.orElse

I use Spring Boot to write a REST service.
I need to return a different entity when an operation succeeds and fails accordingly. ResponseEntity in Spring is parametrized by type T. I know I can omit the type and return just ResponseEntity, but that is not enough when trying to create the response with Java 8 Optional's orElse chain:
public ResponseEntity getDashboard(String user, UUID uuid) {
Optional<Dashboard> dashboard = dashboardService.getDashboard( user, uuid );
// this gives unchecked assignment: 'org.springframework.http.ResponseEntity'
// to 'org.springframework.http.ResponseEntity<my.package.SomeClass>'
return dashboard
.map( ResponseEntity::ok )
.orElse( createNotFoundResponse( uuid, "No such object" ) );
}
public static <T> ResponseEntity createNotFoundResp( T entity, String message ) {
ResponseMessage<T> responseMessage = new ResponseMessage<>( message, entity );
return ResponseEntity.status( HttpStatus.NOT_FOUND ).body( responseMessage );
}
Due to Java compiler's type inference orElse clause should return the same type as when the optional is not empty, i.e. ResponseEntity<Dashboard> and not ResponseEntity<ResponseMessage>. I tried to subvert this problem, by providing different return paths like this:
if ( dashboard.isPresent() ) {
return ResponseEntity.ok( dashboard.get() );
} else {
return createNotFoundResponse( uuid, "No such object" );
}
...but then Intellij highlights the dashboard.isPresent() part and shouts that this block can be simplified to the one above (which results in unchecked warning).
Is there a way to write this code cleanly without any compiler warnings and #SuppressUnchecked annotations?
Is there a way to write this code cleanly without any compiler warnings and #SuppressUnchecked annotations?
I don't think you can get rid of compiler warnings in this case. One of possible clean solutions (at least, no compiler warnings) is rejecting the idea of Optional.map in favor of a simple if/else or ?:-driven strategy not available with fluent interfaces though.
static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final Supplier<? extends U> orElse) {
return okOrNotFound(optional, "Not found", orElse);
}
static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final String message, final Supplier<? extends U> orElse) {
return optional.isPresent()
? status(OK).body(optional.get())
: status(NOT_FOUND).body(new NotFound<>(orElse.get(), message));
}
#RequestMapping(method = GET, value = "/")
ResponseEntity<?> get(
#RequestParam("user") final String user,
#RequestParam("uuid") final UUID uuid
) {
final Optional<Dashboard> dashboard = dashboardService.getDashboard(user, uuid);
return okOrNotFound(dashboard, () -> uuid);
}
Note orElse is not really what you wanted: orElseGet is lazy and only invokes its supplier if the given optional value is not present.
However, Spring features a better way to accomplish what you need and I believe a cleaner way of doing the things like that. Take a look at controller advices that are designed for such purposes.
// I would prefer a checked exception having a super class like ContractException
// However you can superclass this one into your custom super exception to serve various purposes and contain exception-related data to be de-structured below
final class NotFoundException
extends NoSuchElementException {
private final Object entity;
private NotFoundException(final Object entity) {
this.entity = entity;
}
static NotFoundException notFoundException(final Object entity) {
return new NotFoundException(entity);
}
Object getEntity() {
return entity;
}
}
Now the REST controller method becomes:
#RequestMapping(method = GET, value = "/")
Dashboard get(
#RequestParam("user") final String user,
#RequestParam("uuid") final UUID uuid
) {
return dashboardService.getDashboard(user, uuid)
.orElseThrow(() -> notFoundException(uuid));
}
Spring is smart enough to convert objects to status(OK).body(T) itself, so we're just throwing an exception containing a single object we are interested in. Next, a sample controller exception advice might look as follows:
#ControllerAdvice
final class ExceptionControllerAdvice {
#ExceptionHandler(NotFoundException.class)
ResponseEntity<NotFound<?>> acceptNotFoundException(final NotFoundException ex) {
return status(NOT_FOUND).body(notFound(ex));
}
}
where notFound() method is implemented like this:
static NotFound<?> notFound(final NotFoundException ex) {
return notFound(ex, "Not found");
}
static NotFound<?> notFound(final NotFoundException ex, final String message) {
return new NotFound<>(ex.getEntity(), message);
}
For my spike project provides the following results:
_http://localhost:8080/?user=owner&uuid=00000000-0000-0000-0000-000000000000 - {"description":"dashboard owned by owner"}
_http://localhost:8080/?user=user&uuid=00000000-0000-0000-0000-000000000000 - {"entity":"00000000-0000-0000-0000-000000000000","message":"Not found"}

Java - Break execution sequence using CompletableFuture chain

I am new to Java and am using CompletableFutures to perform async operations such as below:
public CompletionStage<Either<ErrorResponse, Response>> insertOrUpdate(String actor, String key) {
return this.objectDAO.getByKey(key)
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
if (finished) {
Response response = Response.ok().build();
return Either.right(response);
} else {
return Either.left(ErrorResponse.create("Error", 400));
}
});
}
Now I need to modify this so that if the get fails then I perform the above chain, but if it succeeds then I need to break this chain and return from the function with an Either object containing an ErrorResponse.
How can I break this processing chain? I know I can pass a flag to each function in the chain and achieve this by performing the actions in the functions based on the value of the flag. I was hoping there is a better way to do this.
Thanks!!
I would rewrite your code.
Don't use Either for errors, Java has exception
Don't return a CompletionStage from your DAO
Use exceptionally from CompletableFuture, it is designed for this
Then do this:
public CompletionStage<Response> insertOrUpdate(String actor, String key) {
return CompletableFuture.supplyAsync(() -> this.objectDAO.getByKey(key))
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
Response response = Response.ok().build();
return response;
})
.exceptionally(e -> ErrorResponse.create("Error", 400));
}
The DAO should be something like this:
class ObjectDAO {
public Object getByKey(String key) {
if (keyNotFound) {
throw new NoSuchElementException();
}
return new Object();
}
}
You may have to make sure that ErrorResponse is a subclass of Response to make this work.

Categories