Define what method to call based on value of Optional - java

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))

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
}

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

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;
}

Extract/Subscribe to Mono value for return type on generated interface method

I have an interface that I am implementing that is generated from a WSDL, so I can't change the signatures. One of the methods returns a List<Attributes> with Attributes being another WSDL generated class. The call flow has been updated to be reactive, and the Mono is bubbled all the way up the chain to this implementing class.
public List<Attributes> getUserProfile(final String filterValue);
The return statement calls a method that returns a Mono<UserProfile> which is then passed to another method that returns the List, but I cannot figure out how to subscribe to this to get the List so that my code compiles and works in a non-blocking way?
#Override
public List<Attributes> getUserProfile(final String filterValue) {
return getUserProfile(serviceName, filterValue)
.map(userProfile -> userProfileToAttributeList(userProfile));
// .subscribe(userProfile -> userProfileToAttributeList(userProfile));
// .map(listAttributes -> listAttributes);
// Mono<UserProfile> userProfileMono = getUserProfile(serviceName, filterValue);
// return userProfileMono.subscribe(userProfile -> userProfileToAttributeList(userProfile));
}
private Mono<UserProfile> getUserProfile(final String serviceName,final String filterValue) {
return myService.getUserProfile(serviceName, filterValue);
}
private List<Attributes> userProfileToAttributeList(final UserProfile userProfile) {
return userProfile.getAttributes().stream().map(MyServiceEndpoint::newAttribute).collect(Collectors.toList());
}
Since you can't change the method signature to return Mono or Flux there is no other way around it, you need to block to get the "real" List<Attributes>.
Reactive only works if the complete operation is reactive on each step. Since this method "says" it will return List<Attributes> and not "promises" to return a List<Attributes> at some point in the future when needed and requested you have no option here.

How to update multiple mongo collections inside the same function in spring webflux?

There are 2 collections in my mongoDB:
1.users collection which contains user phone number and wwhether it is verified or not?
{
_id: '12123',
phones: [
{
phoneNumber: '1234567890',
verified: false
}
]
}
2. verificationTokens collection which contains the verification code mapped by id from user collection as userId.
{
_id: '1111',
userId: '12123',
token: '4545'
}
I'm creating an endpoint in Spring WebFlux to verify the phone number. The endpoint receives the userId and verificationCode. If the token in the collection matches the token sent by the user, then update the verified to true in user collection.
I'm trying to write a single function that would be called by this endpoint and make the required changes.
I tried in the following code but the verified status is not updating to true.
public Mono<VerifyPhoneToken> verifyPhoneNumber(String id, String verificationCode) {
return verifyPhoneTokensRepository.findByUserId(id)
.flatMap(vpt -> {
if (verificationCode.equals(vpt.getToken())) {
usersRepository.findById(id)
.flatMap(user -> {
user.getPhones().get(0).setVerified(true);
return usersRepository.save(user);
});
return verifyPhoneTokensRepository.save(vpt);
}
return null;
});
}
Also, I would like to know if return null can be handled in a better way.
im guessing your problem is when you are calling the usersRepository you are not handling the return thus breaking the event chain. You should try something like:
public Mono<VerifyPhoneToken> verifyPhoneNumber(String id, String verificationCode) {
return verifyPhoneTokensRepository.findByUserId(id)
.flatMap(vpt -> {
if (verificationCode.equals(vpt.getToken())) {
return updateUser(id)
.then(verifyPhoneTokensRepository.save(vpt));
}
return Mono.empty();
});
}
private Mono<User> updateUser(String id) {
return usersRepository.findById(id)
.flatMap(user -> {
user.getPhones().get(0).setVerified(true);
return usersRepository.save(user);
});
}
using then to chain on your action. Also dont return null return Mono<Void> (Mono.empty())

Asynchronous sequential calls based on condition checks in reactor

Here, I am trying to make asynchronous and non-blocking calls using reactor and for each request, I may have to call two services in sequence (in my case below, getAccountInfoFromAAA and getAccountInfoFromBBB).
Here is my ItemRequest object:
public class ItemRequest {
private Account account;
private Result firstServiceResult;
private Result secondServiceResult;
private PostingParameterCode postingParameterCode; //enum
//...
//...
//getters and setters
}
So, my request input will contain multiple itemRequests and for each itemRequest, I am doing asynchronous calls as:
public void getAccountData(List<ItemRequest> itemRequests) {
ImmutableList<ItemRequest> list = ImmutableList.copyOf(itemRequests);
Flux.fromIterable(list).flatMap(this::callBothSors).blockLast();
}
public Mono<ItemRequest> callBothSors(ItemRequest itemRequest) {
return getAccountDataService.getAccountDataFromAAAandBBB(itemRequest);
//here, it will enter into a sequential call for each itemRequest
}
This is my first service call interface:
public Mono<ItemRequest> getAccountDataFromAAA(ItemRequest itemRequest);
This is my second service call interface:
public Mono<ItemRequest> getAccountDataFromBBB(ItemRequest itemRequest);
This method will have upto two calls in sequence based on the condition:
public Mono<ItemRequest> getAccountDataFromAAAandBBB(ItemRequest itemRequest){
Mono<ItemRequest> firstCallResult = Mono.empty();
Mono<ItemRequest> secondCallResult = Mono.empty();
if(isFirstServiceCallRequired(itemRequest)){
firstCallResult = this.firstServiceCallImpl.getAccountDataFromAAA(itemRequest);
//basically, firstService call will update the accountKey information and
//will also set the result status to OK which is required to decide
//whether to make secondService call.
} else {
//Account key is already present, so just update the result status which I need later.
Result result = new Result();
result.setStatus(Result.Status.OK);
result.setMessageText("First call not required as account info is set for item request");
itemRequest.setFirstServiceResult(result);
}
//Now, before calling the second service, I need to check the following:
if(null!= itemRequest.getFirstServiceResult() &&
itemRequest.getFirstServiceResult().getStatus().equals(Result.Status.OK) &&
itemRequest.getPostingParameterCode().equals(PostingParameterCode.MOBILECREDIT)){
secondCallResult = this.secondServiceCallImpl.getAccountDataFromBBB(itemRequest);
}
return firstCallResult.then(secondCallResult); //attaching the
//firstCallResult and secondCallResult to produce a single Mono
}
This is working fine when firstCallResult is not required. But when the first call is required, this condition check will not pass since I won't have first call result object updated:
if(null != itemRequest.getFirstServiceResult() &&
itemRequest.getFirstServiceResult().getStatus().equals(Result.Status.OK) &&
itemRequest.getPostingParameterCode().equals(PostingParameterCode.MOBILECREDIT))) { ... }
//this condition check will not pass because first service call is not actually executing
Both cases works fine if I put the following statement:
if(isFirstServiceCallRequired(itemRequest)){
firstCallResult = this.firstServiceCallImpl.getAccountDataFromAAA(itemRequest);
firstCallResult.block(); //adding this case will work on both cases
}
But, I don't think I will get the reactors benefit this way.
I was thinking to have the logic like this:
Mono<ItemRequest> result = firstService.call(...)
.doOnNext(/*do something */)
.then( ... secondService.call())
But couldn't figure out the way to chain the secondService with firstService to get the mono result and have those condition checks too.
Condition check is important since I don't always want to execute the second service. Is there any way to chain the secondService with firstService to get the result and have those condition checks too?
Apologies for the long question. Any suggestions/help would be greatly appreciated.
After offering the bounty points to this question, I was really excited and expecting some answers.
But anyways, I am able to improve my initial solution and have those condition checks too.
I did the following:
I changed the return type from Mono<ItemRequest> to Mono<Void> in both service calls since I am basically updating the data to ItemRequest list:
Handling the parallel call here (each parallel call has a sequential call):
public void getAccountData(List<ItemRequest> itemRequests) {
ImmutableList<ItemRequest> list = ImmutableList.copyOf(itemRequests);
Flux.fromIterable(list).flatMap(this::callBothSors).blockLast();
}
public Mono<Void> callBothSors(ItemRequest itemRequest) {
return getAccountDataService.getAccountDataFromAAAandBBB(itemRequest);
//here, it will enter into a sequential call for each itemRequest
}
and these are my firstServiceCall and secondServiceCall interface changes:
public Mono<Void> getAccountDataFromAAA(ItemRequest itemRequest);
public Mono<Void> getAccountDataFromBBB(ItemRequest itemRequest);
and I chained the secondServiceCall with firstServiceCall to get the mono result and have those condition checks too as:
public Mono<Void> getAccountDataFromAAAandBBB(ItemRequest itemRequest){
Mono<Void> callSequence = Mono.empty();
if(isFirstServiceCallRequired(itemRequest)){
callSequence = this.firstServiceCallImpl.getAccountDataFromAAA(itemRequest);
} else {
//Account key is already present, so just update the result status which I need later.
Result result = new Result();
result.setStatus(Result.Status.OK);
result.setMessageText("First call not required as account info is set for item request");
itemRequest.setFirstServiceResult(result);
}
return callSequence.thenEmpty(Mono.defer(() -> {
//note: Mono.defer ==>> Create a Mono provider that will supply a target Mono to subscribe to
//for each subscriber downstream.
//only if the firstServiceCall result is successful & other condition check successful,
// I am calling secondServiceCall:
if(shouldCallSecondService(itemRequest)){
return this.secondServiceCallImpl.getAccountDataFromAAAandBBB(itemRequest);
} else {
return Mono.empty();
}
}))
Here are some news: A Reactor is not a silver bullet! :)
Whenever you need the response of a call to determine if you need to do something else, this will never be able to be fully parallelized. E.g. you could always do you last suggestion. However, this doesn't mean that using the Reactor doesn't give you any benefits!
Some of the benefits you get:
You are using Netty under the hood instead of Servlet, which helps to avoid locking on I/O operations. This can lead to better allocation of resources, making your system more resilient.
You can do other operations while waiting for a response. If you have things to do where the order doesn't matter, you can always put them there (e.g. auditing, logging etc).
I hope this answers your question :)
public Mono<ItemRequest> getAccountDataFromAAAandBBB(ItemRequest itemRequest) {
Mono<ItemRequest> firstCallResult = Mono.empty();
Mono<ItemRequest> secondCallResult = Mono.empty();
if (isFirstServiceCallRequired(itemRequest)) {
firstCallResult = this.firstServiceCallImpl.getAccountDataFromAAA(itemRequest);
//basically, firstService call will update the accountKey information and
//will also set the result status to OK which is required to decide
//whether to make secondService call.
} else {
/*Account key is already present, so just update the result status which I need
later.*/
firstCallResult = Mono.defer(() -> {
Result result = new Result();
result.setStatus(Result.Status.OK);
result.setMessageText("First call not required as account info is set for item request");
itemRequest.setFirstServiceResult(result);
return Mono.just(itemRequest);
});
}
return firstCallResult.flatMap(itReq -> {
//Now, before calling the second service, I need to check the following:
if (null != itemRequest.getFirstServiceResult() &&
itemRequest.getFirstServiceResult().getStatus().equals(Result.Status.OK) &&
itemRequest.getPostingParameterCode().equals(PostingParameterCode.MOBILECREDIT)) {
return secondCallResult = this.secondServiceCallImpl.getAccountDataFromBBB(itemRequest);
} else {
return itReq;
}
});
}
The next simple example can help you with flatMap understanding:
public static void main(String[] args) {
callExternalServiceA.flatMap(response -> {
if(response.equals("200")){
return Mono.just(response);
} else {
return callExtertnalServiceB();
}
}).block();
}
public static Mono<String> callExtertnalServiceA() {
return Mono.defer(() -> {
System.out.println("Call external service A");
return Mono.just("400");
});
}
public static Mono<String> callExtertnalServiceB() {
return Mono.defer(() -> {
System.out.println("Call external service B");
return Mono.just("200");
});
}

Categories