I have 2 Single. In first I receive token and in second I need use it, and then I must save some info from second single and return completable.
I thought do this with completable and andThen, like this:
String token;
IStoreProvider storeProvider;
IWebProvider webProvider;
public Completable getUserInfo(){
return Completable.fromSingle(Completable
.fromSingle(storeProvider
.getToken()
.doOnSuccess(x->token=x))
.andThen(webProvider.getUserInfo(token)
.doOnSuccess(x->storeProvider.saveUserInfo(x)));
interface IStoreProvider{
Single<String> getToken();
Completable saveUserInfo(UserInfo userInfo);
}
interface IWebProvider{
Single<UserInfo> getUserInfo(token);
}
But its dosen't work. How I can do it?
Your stream is created before its executed, so during creation token=null and therefore you will get null in getUserInfo.
flatMap will help you.
public Completable getUserInfo() {
return storeProvider.getToken()
.flatMap(token -> webProvider.getUserInfo(token))
.flatMapCompletable(userInfo -> storeProvider.saveUserInfo(userInfo));
}
Related
I know that my question has been asked already multiple times, but I feel like there is still no satisfying answer to it.
Basically I have two downstream services which I want to call (in parallel) and then I want to combine the results and return it (as Json). Both calls can fail but both results are not mandatory, so also an empty combined response is possible:
class FirstResponse {...}
class SecondResponse {...}
class CombinedResponse {
private FirstResponse first;
private SecondResponse second;
}
class FirstService {
Mono<FirstResponse> get(){
return webclient.get(...)
.bodyToMono(FirstResponse.class)
.onErrorResume(throwable -> Mono.empty);
}
}
class SecondService {
Mono<SecondResponse> get(){
return webclient.get(...)
.bodyToMono(SecondResponse.class)
.onErrorResume(throwable -> Mono.empty);
}
}
#RestController(...)
class CombinationController {
#GetMapping(...)
Mono<CombinedResponse> getCombined() {
Mono.zip(firstService.get(), secondService.get(), (first, second) -> {
return new CombinedResponse(first, second);
})
}
}
Now in case the calls to firstService fails, also the response from secondService gets ignored. But what I actually would like to have, is that CombinedResponse still gets (partially populated).
As a disclaimer I have to say, that I am currently migrating my code from rxjava1 and there in case of downstream errors I just return Single.just(null). This allows me to zip both results and just sets the values to null.
About Mono.zip() :
An error or empty completion of any source will cause other sources to
be cancelled and the resulting Mono to immediately error or complete,
respectively.
Also, reactor does not allow null values, so you should do some workaround in your case. In some simple cases it is easy to define some default value in case of error (for example, empty String), but for custom types it would be weird to create an empty object.
As an alternative for such cases I would suggest to use Optional.
This solution adds some boilerplate code, though.
First service:
class FirstService {
Mono<Optional<FirstResponse>> get(){
return webclient.get(...)
.bodyToMono(FirstResponse.class)
.map(Optional::of)
.onErrorReturn(Optional.empty());
}
}
Second service:
class SecondService {
Mono<Optional<SecondResponse>> get(){
return webclient.get(...)
.bodyToMono(SecondResponse.class)
.map(Optional::of)
.onErrorReturn(Optional.empty());
}
}
And "combiner" :
#GetMapping(...)
Mono<CombinedResponse> getCombined() {
Mono.zip(firstService.get(), secondService.get())
.map(tuple -> {
// check optionals here from tuple.getT1() and tuple.getT2()
// and do whatever you want
})
...
}
I have a method called fluxFromFileStream that returns a Flux of String. after handling those string and doing some DTO operations, I have to save them into a mongodb database using methods transformAndSaveKpis(kpiHdfsDtoFlux) and transformAndSaveReports(kpiHdfsDtoFlux).
public void transformAndSaveKpisAndReports(InputStream inputStream, DQSCJobName jobName) {
fluxFromFileStream(inputStream)
.flatMap(this::buildDTOFromLine)
.map(kpiHdfsDto -> changeKpiTypeToFreezeIfJobNameIsFrozen(jobName, kpiHdfsDto))
.cache()
.transform(
kpiHdfsDtoFlux -> Flux.zip(transformAndSaveKpis(kpiHdfsDtoFlux), transformAndSaveReports(kpiHdfsDtoFlux))
);
}
The problem with that is the method .transform() is returning a value of flux that I will not need. what I need actually is to verify if the reactive stream has been achieved successfully without any problem, else throw an exception inside my main method (transformAndSaveKpisAndReports).
Before, I was checking the result of the whole stream (including .transform) if it's null then throw an exception, but appears to me that's not really the clean way to do things.
bellow are the methods I'm calling inside the transform method:
private Flux<Kpi> transformAndSaveKpis(Flux<KpiHdfsDto> kpiHdfsDtoFlux) {
return kpiHdfsDtoFlux
.map(this::kpiHdfsDtoToKpiDocument)
.collectList()
.flatMapMany(kpis -> kpiRepository.insertAll(kpis));
}
private Flux<Report> transformAndSaveReports(Flux<KpiHdfsDto> kpiHdfsDtoFlux) {
return kpiHdfsDtoFlux
.flatMap(this::kpiHdfsDtoToReportDocument)
.groupBy(Report::getType)
.flatMap(reportList -> reportRepository.insertAll(reportList.collectList()));
}
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 am trying to convert Void and Response type to Observable corresponding. I tried .just but .create not sure if I can use just or create to do this conversion. Thanks.
void getSomeValue(){
Observable<Response> returnedObservable=getResponse();//How to convert this from Response to Observable<Response>
Observable<Void> returnedObservable=doSomething();//How to convert this from Void to Observable<Void>
}
Void doSomething(){
//some code...
return null;
}
Response getResponse(){
//some code....
return someResponse;
}
Small note: RxJava provides several types of observables (see here). For this case, there is no reason for returning an Observable<Void>, you can use a Completable: in fact, it represents a deferred computation without any value.
You can use the method fromCallable: this defers the execution of getResponse function until the Observable is subscribed. The eager approach consists to use just: it evaluates the function immediately in the current thread.
Observable<Response> response = Observable.fromCallable(this::getResponse);
Observable<Response> response = Observable.just(getResponse());
Same for Completable:
Completable something = Completable.fromAction(this::doSomething);
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.