having the following code that produces a continuous flux of random strings :
#RestController
public class WebFluxController {
private final Random random = new Random();
#CrossOrigin
#GetMapping(value = "/documents")
public Flux getDocuments() {
return Flux.interval(Duration.ofSeconds(1))
.map(x -> "document-" +
random.nextDouble());
}
}
...how can I replace the random with a query to the database that will return a field of the last record, something like :
#RestController
public class WebFluxController {
#Autowired
private ReactiveDocumentRepository reactiveDocumentRepository;
#CrossOrigin
#GetMapping(value = "/documents")
public Flux getDocuments() {
return Flux.interval(Duration.ofSeconds(1))
.map(x -> "document-" +
reactiveDocumentRepository.findLastDocument().map(d->d.getDescription);
}
}
}
...where
reactiveDocumentRepository.findLastDocument() returns a mono containing last document inserted in the db?
In other words, I want that query to be ran continuously over the database and publish last inserted record all the time
In reactive you need to build a flow using operators that will be evaluated when downstream (in your case webflux) subscribes to the flow. Result is not immediately available and you can't just concatenate it with a string. reactiveDocumentRepository.findLastDocument() is reactive and you need to use flatMap instead of map.
public Flux getDocuments() {
return Flux.interval(Duration.ofSeconds(1))
.flatMap(x ->
reactiveDocumentRepository.findLastDocument()
.map(d -> "document-" + d.getDescription)
);
}
Related
I need to make an asynchronous call and use some values present in it to make multiple calls to same service. Combine the response on these calls with the first one and return.
For example When I make the first call I get below JSON, which has a list of ids. Now I have to make multiple calls to a service with these ids and make a list of their response and send it to downstream by appending it in same JSON.
{“id”: 145,
“object”:[{“id”:111}]
}
I have tried using zipWhen and
Flux.fromIterable(userIds).parallel().runOn(Schedulers.elastic()).flatMap()
But resulting list always comes as empty or null. How can we achieve this? Am I missing something here?
Edit1:
Resolved it by using Flux.fromIterable. Read more about it and finally understood the use and resolved it. The below method takes up one item from list and will call the inner method which will call multiple APIs:
return Flux.fromIterable(somelist).flatMap(listItem -> {
return someMethodToCallAnotherAPIUsingZipWith(listItem);
}).collectList();
Inner Method:
It calls 1st API, passed its result to zipWith and using this result we can call another API or we can simply use it with its response.
private Mono<Object> someMethodToCallAnotherAPIUsingZipWith(String listItem) {
return authService.getAccessToken().flatMap(accessToken ->
webClient.get().uri(builder -> builder.path("/path").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve()
.toEntity(Entity.class).log()
.flatMap(entity -> {
//manipulate your response or create new object using it
return Mono.just(entity);
}).zipWhen(consent -> webClient.get().uri(builder -> builder.path("/otherpath").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve().bodyToMono(Entity.class).log()
.flatMap(entity -> {
//example
listItem = entity.getString();
return Mono.just(listItem);
}), (string1, string2) -> string1 + string2));
}
private Mono<Object> someMethodToCallAnotherAPIUsingZipWith(String listItem) {
return authService.getAccessToken().flatMap(accessToken ->
webClient.get().uri(builder -> builder.path("/path").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve()
.toEntity(Entity.class).log()
.flatMap(entity -> {
//manipulate your response or create new object using it
return Mono.just(entity);
}).zipWhen(consent -> webClient.get().uri(builder -> builder.path("/otherpath").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve().bodyToMono(Entity.class).log()
.flatMap(entity -> {
//example
listItem = entity.getString();
return Mono.just(listItem);
}), (string1, string2) -> string1 + string2));
}
I am new to spring webflux and have a problem with aggregating a flux to a Mono.
ProductController has a method Flux<Product> get(List<UUID> ids) returning a Stream of Products for a given list of ids. When all products have been fetched the flux completes.
Aggregator fetches a list of products, computes a new ProductAggregateDTO from the stream and sends it to an accountingService, which then processes them and assigns an UUID to the accounting process.
class Aggregator {
Mono<UUID> process(List<UUID> ids) {
ProductAggregateDTO adto = new ProductAggregateDTO();
productAdapter.getProducts(ids)
.doOnNext(e -> {
adto.consume(e);
})
.doOnComplete(() -> {
Mono<UUID> processId = accountAdapter.process(adto);
})
.subscribe();
}
}
I want to return processId from the process function. I don't think thats a big problem. But I can not find how.
Thanks for your help!
Kind Regards,
Andreas
I'm kinda stuck with a trivial task: whenever I query an external API with reactive spring WebClient or query reactive MongoDBRepository, I'd like to log how many entities got through my flux, eg. to log message like "Found n records in the database.". Eg:
return repository.findAll()
.doOnComplete { log.info("Found total n records!") } // how to get the n?
.filter { it.age > 10 }
.distinct { it.name }
TLDR: How to get a flux size (and perhaps it's contents) when it completes?
You can use ConnectableFlux. In your example:
var all = repository.findAll()
.filter { it.age > 10 }
.distinct { it.name }
.publish()
.autoConnect(2)
all.count()
.subscribe {c -> log.info("Found total {} records!", c)}
return all;
By calling the count(). It should emit a Mono when onComplete is observed.
Here was what I did,
AtomicInteger i = new AtomicInteger();
Flux<UserDetails> stringFlux =
Flux.using(() -> stringStream, Flux::fromStream,
Stream::close)
.doOnNext(s -> i.getAndIncrement())
.log()
.map(UserDetails::createUserDetails);
stringFlux
.subscribe(updateUserDetailsService::updateUserDetails);
log.info("number of records: {}", i);
How to combine multiple results emmited by observables into one result and emit it once?
I have a Retrofit service:
public interface MyService {
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id);
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id, #Query("page") int pageId);
}
And I have a model:
public class UserPostsResult {
#SerializedName("posts")
List<UserPost> mPosts;
#SerializedName("nextPage")
int mPageId;
}
Also I have ids List<Integer> friendsIds;
My goal is to have a method like this one:
public Observable<Feed> /*or Single<Feed>*/ getFeed(List<Integer> ids) {
...
}
It returns one Observable or Single that does the following:
Combines all getUserPosts(idFromList) to one observable
For each UserPostsResult must do:
if (userPostResult.mPageId > -1)
getUserPosts(currentUserId, userPostResult.mPageId);
And merge this result to the previous userPostResult
Return one single model as result of all operations.
Result class:
public class Feed {
List<UserPost> mAllPostsForEachUser;
}
EDIT (More details):
My client specifications was that I must take from social network user posts with no logging in, no token requesting. So I must parse HTML pages. That's why I have this complex structure.
EDIT (Partial solution)
public Single<List<Post>> getFeed(List<User> users) {
return Observable.fromIterable(users)
.flatMap(user-> mService.getUserPosts(user.getId())
.flatMap(Observable::fromIterable))
.toList()
.doOnSuccess(list -> Collections.sort(list, (o1, o2) ->
Long.compare(o1.getTimestamp(), o2.getTimestamp())
));
}
This solution doesn't include pages problem. Thats why it is only partial solution
There are a number of operators which transform things into other things. fromIterable() will emit each item in the iterable, and flatMap() will convert one type of observable into another type of observable and emit those results.
Observable.fromIterable( friendsIds )
.flatMap( id -> getUserPosts( id ) )
.flatMap( userPostResult -> userPostResult.mPageId
? getUserPosts(currentUserId, userPostResult.mPageId)
: Observable.empty() )
.toList()
.subscribe( posts -> mAllPostsForEachUser = posts);
If you need join two response in one you should use Single.zip
Single.zip(firsSingle.execute(inputParams), secondSingle.execute(inputPrams),
BiFunction<FirstResponse, SecondResponse, ResponseEmitted> { firstResponse, secondResponse ->
//here you put your code
return responseEmmitted
}
}).subscribe({ response -> },{ })
I am using RxJava in which I want to dynamically create a number of Observables based on some condition. Once I'm done with creating, I want to do some processing on the different values returned by the observables and then send as a single Observable to which I can subscribe on. Here is how my code is :
List<String> valueList = ....
List<Observable<String>> listOfObservables = new ArrayList<Observable<String>>();
for(int i =; i <valueList.size(); i++){
listOfObservables.add(new SomeClass.doOperation(valueList(i)));
// SomeClass.doOperation will return an Observable<String>
}
return Observable.merge(listOfObservables);
But here , I want to do some operation on the values emitted by different Observables in the listOfObservable and finally return it as a single Observable<String>
Like in Observable.zip() , I can do this like
return Observable.zip(observable1, observable2, (string1, string2) -> {
// joining final string here
return string1 + string2;
But I know the number of arguments here. Please let me know how I can achieve this.
Use the zip overload that takes a variable number of arguments, it has a signature of
<R> Observable<R> zip(Iterable<? extends Observable<?>> ws,
FuncN<? extends R> zipFunction)
Example usage:
List<String> valueList = ....
return Observable.from(valueList)
.map(string -> SomeClass.doOperationThatReturnsObservable(string))
.toList()
.flatMap(listOfObs -> Observable.zip(listOfObs, (Object[] results) -> {
// do something with the strings in the array.
return Arrays.stream(results)
.map(Object::toString)
.collect(Collectors.joining(","));
}));