I'm trying to figure out how to get a result from the network, persist it and return the reponse body to the Observable.
Like this:
#Override
public Observable<DefaultUserResponse> createUser(CreateUserCommand command) {
return this.userService.createUser(command)
.map(defaultUserResponse -> {
User user = new User();
defaultUserResponse.setUser(user);
return defaultUserResponse;
}).flatMap(defaultUserResponse -> persist(defaultUserResponse.getUser()));
}
Observable<User> persist(User user) {return null;}
Steps:
I get a DefaultUserResponse from the server.
Transform the command to a User (command = DTO)
Persist the user locally.
Return the DefaultUserResponse.
How should I proceed ?
Thanks
Consider using Completable type for your persist method (if you don't care about the returned value(s)):
Completable persist(User user) {
return Completable.fromCallable(() -> {
//persisting
return null;
}
}
Then you could use andThen operator to wait until persisting completes and push your value further:
...
.flatMap(defaultUserResponse -> persist(defaultUserResponse.getUser()).andThen(Observable.just(defaultUserResponse)));
Or if you still want to use Observable for persisting, just use another flatMap instead of andThen.
Related
I have a DTO class like this :
public class User {
#Field("id")
private String id;
private String userName;
private String emailId;
}
I have to provide an update and delete feature through API.
I have written the following code to delete the record:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
RemoveUserDetails method is something like this :
public Mono<User> removeUserDetails(User userObj) {
return findByUsername(userObj.getUsername())
.flatMap(existingUser -> {
// logic to delete the data from database which working as expected
}).switchIfEmpty(
Mono.defer(() -> {
return Mono.error(new Exception("User Name " + userObj.getUsername() + " doesn't exist."));
})
);
}
The problem with this code is even if the user is not existing, it is not showing the Mono error I'm returning. In every case, this always returns "Remove Successful".
How can I change my service layer method so that it can return whatever is received by the repo method? I'm new to Reactor code, so unable to figure out how to write it.
Whenever you call subscribe, consider it an immediate red flag. Subscription is something that should be handled by the framework you're using (Webflux in this case.)
If you subscribe yourself, such as in this example:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
...then you've essentially created a "fire and forget" type subscription, where you have no way of knowing if that publisher completed successfully, if it caused an error, how long it took to complete, whether it completed at all, or whether it emitted an element. So in this case, you're saying "send a request to remove user details, forget you sent it, and then before waiting for any kind of result, always return 'Remove successful'." This is almost never what you want.
You could use something like:
public Mono<String> userData(User body) {
return repo.removeUserDetails(userObj)
.then(Mono.just("Remove Successful"));
}
...which is much better as it includes everything as part of the reactive chain. In this case, you'll either get an error signal, or you'll get "Remove Successful".
However, chances are you don't need that String to be returned at all - you just need to know if it's successful or not. The standard way of doing that (I just need to know that it's completed successfully or not, I don't need it to return a value) is to use Mono<Void> as the return type and then(), something like:
public Mono<Void> userData(User body) {
return repo.removeUserDetails(userObj).then();
}
...which will give you a standard completion if the deletion was successful, and an error signal otherwise.
A common pattern you find when using reactive java code is handling nulls when collecting a list.
The following code is a simple example showing how to handle nulls returned by a Location by wrapping getLocation in a Mono.defer then handling a null using onErrorReturn.
The test code
List<String> items = inventory.testList().block();
items.forEach(System.out::println);
USA
Not Found
SPAIN
private List<Integer> clusters;
private List<Mono<Location>> locations;
private List<String> countryCodes;
public Mono<List<String>> testList() {
clusters = Arrays.asList(0, 1, 2);
locations = Arrays.asList(Mono.just(new Location(0)), null, Mono.just(new Location(2)));
countryCodes = Arrays.asList("USA", "FRANCE", "SPAIN");
return Flux.fromIterable(clusters)
.flatMap(cluster -> getLocation(cluster))
.collectList();
}
public Mono<String> getLocation(int clusterID) {
return Mono.defer(() -> locations.get(clusterID))
.flatMap(location -> Mono.just(location.id))
.flatMap(id -> Mono.just(countryCodes.get(id)))
.onErrorReturn(Exception.class, "Not Found");
}
I have an interesting problem which I don't know how to solve without calling a block() method.
my method receives a user as an argument.
it calls an external service and receives a Mono
if Mono does not contain an error and user.getDepartment().startsWith("Development") I want to add this to the Flux
Flux should be a result of my method
For now I unfortunatelly should initialize Flux from the list before calling the block()-method for Mono that I receive:
Flux<User> getUsers(User user) {
List<Users> developmentUsers = new ArrayList<User>();
while (user.containsManager()) {
val resultUser = externalUserServiceClient.getManager(user).block(); //externalUserServiceClient.getManager(user) should return a Mono<User>
if (resultUser.getDepartment().startsWith("Development"))
developemtnUsers.add(resultUser);
user = resultUser;
}
return Flux.fromIterable(developmentUsers);
}
I am sure there should be a way not to interrupt the async processes chain. Do you know how?
You can use Mono#expand that recursively applies getManager function and combines results into Flux<User>:
Flux<User> getUsers(User user) {
return getManager(user).expand(manager -> getManager(manager));
}
Mono<User> getManager(User user) {
if (user.containsManager()) {
return externalUserServiceClient.getManager(user)
.filter(manager -> manager.getDepartment().startsWith("Development"));
} else {
return Mono.empty();
}
}
By using
externalUserServiceClient.getManager(user).map(manager -> ...);
//or
externalUserServiceClient.getManager(user).flatMap(manager -> ...);
I am still new to Spring Webflux and flatMap on Mono doesn't seem to work.
I have the following function and call to kafkaPublisher.publishToTopic is not working. I inserted the print statement to test if it prints anything and it doesn't even execute the print statement. publishToTopic returns Mono<Void>.
private Mono<Void> test(Long gId, UUID pId) {
Mono<UUID> nId = pDao.findNId(pId);
Mono<List<String>> channels = nId.flatMapMany(pDao::findChannels).collectList();
return Mono.zip(nId, channels)
.flatMap(t -> {
System.out.println(t.getT1());
return kafkaPublisher.publishToTopic(gId, t.getT1().toString(), t.getT2());
});
}
It gets invoked if .block is called on flatMap as shown below.
private Mono<Void> test(Long gId, UUID pId) {
Mono<UUID> nId = pDao.findNId(pId);
Mono<List<String>> channels = nId.flatMapMany(pDao::findChannels).collectList();
Mono.zip(nId, channels)
.flatMap(t -> {
System.out.println(t.getT1());
return kafkaPublisher.publishToTopic(gId, t.getT1().toString(), t.getT2());
}).block();
return Mono.empty();
}
I found my mistake. I wasn't not using the result of test anywhere in the function where I was calling this test method. Here is the code I was using to call test
public Mono<Void> saveNew(NewPre pre) {
preDao.insert(pre)
.flatMap(p -> test(p.pId(), p.nId()));
return Mono.empty();
}
I changed it to following and it works.
public Mono<Void> saveNew(NewPre pre) {
return preDao.insert(preference)
.flatMap(p -> test(p.p(), p.n())
.then(Mono.empty()));
}
flatMap hangs indefinitely, Best way to close the Asynchronous operation by converting it into future object .
public Mono<Void> saveNew(NewPre pre) {
return preDao.insert(preference)
.flatMap({
p -> test(p.p(), p.n())
}).toFuture();
}
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");
});
}
i am using RxAndroid/RxJava for the first time and trying to figure out how to implement a chain of requests but each next request made is dependent on the result of the other.
example:
private Boolean isUserEligible(){
..
}
private String registerDevice()
..
}
private String login(){
..
}
As far as i know, the Observable can only execute all of the above methods or one by one like below:
// Fetch from both simultaneously
Observable<String> zipped
= Observable.zip(isUserEligible(), registerDevice(),login(), new Func2<String, String, String>() {
});
Observable<String> concatenated = Observable.concat(isUserEligible(), registerDevice(),login());
what if i want to do something like this
//execute usUserEligible first and if eligible, execute registerDevice, else execute login().
Thanks in advance
Assuming all of these methods return observables, you could write:
Observable<String> response = isUserEligible()
.flatMap(isEligible -> isEligible ? registerDevice() : login());
Without retro-lambda; you could write:
Observable<String> response = isUserEligible()
.flatMap(new Func1<Boolean, Observable<String>>() {
public Observable<String> call(final Boolean isEligible) {
return isEligible ? registerDevice() : login();
}
});
This is a use case for a flatmap.
http://reactivex.io/documentation/operators/flatmap.html
Create the mapping from the first result to a second observable, here you can use the result of the first function to input it into the second.
final Func1<Boolean, Observable<String>> registerFunc = isEligible -> {
return registerDevice(isEligible)
};
Now you have to create your chain of calls and flatMaps: do the first call, and flatmap the resulting Observable with the function you just created. This will again return an Observable. you can keep chaining it here with other flatmaps
isUserEligible().flatMap(registerFunc);
Be aware that all your functions need to return Observables to make this possible.