In RxJava 2 and Reactor there is a switchIfEmptylike method to switch to new flow if there is no elements in current flow.
But when I began to use Minuty, I can not find an alternative when I convert my Quarkus sample to use the Reactive features.
Currently my solution is: in my PostRepository, I use an exception to indicate there is no post found.
public Uni<Post> findById(UUID id) {
return this.client
.preparedQuery("SELECT * FROM posts WHERE id=$1", Tuple.of(id))
.map(RowSet::iterator)
.flatMap(it -> it.hasNext() ? Uni.createFrom().item(rowToPost(it.next())) : Uni.createFrom().failure(()-> new PostNotFoundException()));
}
And catch it in the PostResource.
#Path("{id}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Uni<Response> getPostById(#PathParam("id") final String id) {
return this.posts.findById(UUID.fromString(id))
.map(data -> ok(data).build())
.onFailure(PostNotFoundException.class).recoverWithItem(status(Status.NOT_FOUND).build());
}
How to return an Uni means 0 or 1 element in PostRepository, and use a switchIfEmpty like method in PostResource to build the alternative path for the flow?
Uni cannot be empty in the sense it always contains an item (potentially null).
So, the equivalent of switchIfEmpty is uni.onItem().ifNull().switchTo(() -> ...)
Related
As i am working in a Project where i want to rewrite the Uni to Multi for a method "findall" to get all the mongodb Document from a collection. I tried to rewrite but not able to find a solution
original:
public Uni<List<Book>> findAll(List<String> authors)
{
return getCollection().
find(Filters.all("authors",authors)).map(Book::from).collectItems().asList();
}
What i tried (But not working)
public Multi<Book> findAll(List<String> authors)
{
return getCollection().find(Filters.all("authors",authors)).transform().
byFilteringItemsWith(Objects::nonNull).onCompletion().ifEmpty().
failWith(new NoSuchElementException("couldn't find the Authors")).onItem().transform(Book::from);
}
I suppose you are using the ReactiveMongoClient provided by Quarkus.
In this case, your method should be:
ReactiveMongoClient client;
public ReactiveMongoCollection<Book> getCollection() {
return client.getDatabase("db").getCollection("books", Book.class);
}
public Multi<Book> findAll(List<String> authors) {
return getCollection()
.find(Filters.all("authors",authors))
.onItem().transform(Book::from)
.onCompletion().ifEmpty()
.failWith(new NoSuchElementException("..."));
}
You don't need to do thebyFilteringItemsWith, as a Multi cannot contain null items.
I am new to Quarkus. I am trying to write a REST endpoint using quarkus reactive that receives an input, does some validation, transforms the input to a list and then writes a message to kafka. My understanding was converting everything to Uni/Multi, would result in the execution happening on the I/O thread in async manner. In, the intelliJ logs, I could see that the code is getting executed in a sequential manner in the executor thread. The kafka write happens in its own network thread sequentially, which is increasing latency.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Multi<OutputSample> send(InputSample inputSample) {
ObjectMapper mapper = new ObjectMapper();
//deflateMessage() converts input to a list of inputSample
Multi<InputSample> keys = Multi.createFrom().item(inputSample)
.onItem().transformToMulti(array -> Multi.createFrom().iterable(deflateMessage.deflateMessage(array)))
.concatenate();
return keys.onItem().transformToUniAndMerge(payload -> {
try {
return producer.writeToKafka(payload, mapper);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
});
}
#Inject
#Channel("write")
Emitter<String> emitter;
Uni<OutputSample> writeToKafka(InputSample kafkaPayload, ObjectMapper mapper) throws JsonProcessingException {
String inputSampleJson = mapper.writeValueAsString(kafkaPayload);
return Uni.createFrom().completionStage(emitter.send(inputSampleJson))
.onItem().transform(ignored -> new OutputSample("id", 200, "OK"))
.onFailure().recoverWithItem(new OutputSample("id", 500, "INTERNAL_SERVER_ERROR"));
}
I have been on it for a couple of days. Not sure if doing anything wrong. Any help would be appreciated.
Thanks
mutiny as any other reactive library is designed mainly around data flow control.
That being said, at its heart, it will offer a set of capabilities (generally through some operators) to control flow execution and scheduling. This means that unless you instruct munity objects to go asynchronous, they will simply execute in a sequential (old) fashion.
Execution scheduling is controlled using two operators:
runSubscriptionOn: which will cause the code snippet generating the items (which is generally referred to upstream) to execute on a thread from the specified Executor
emitOn: which will cause subscribing code (which is generally referred to downstream) to execute on a thread from the specified Executor
You can then update your code as follows causing the deflation to go asynchronous:
Multi<InputSample> keys = Multi.createFrom()
.item(inputSample)
.onItem()
.transformToMulti(array -> Multi.createFrom()
.iterable(deflateMessage.deflateMessage(array)))
.runSubscriptionOn(Infrastructure.getDefaultExecutor()) // items will be transformed on a separate thread
.concatenate();
EDIT: Downstream on a separate thread
In order to have the full downstream, transformation and writing to Kafka queue done on a separate thread, you can use the emitOn operator as follows:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Multi<OutputSample> send(InputSample inputSample) {
ObjectMapper mapper = new ObjectMapper();
return Uni.createFrom()
.item(inputSample)
.onItem()
.transformToMulti(array -> Multi.createFrom().iterable(deflateMessage.deflateMessage(array)))
.emitOn(Executors.newFixedThreadPool(5)) // items will be emitted on a separate thread after transformation
.onItem()
.transformToUniAndConcatenate(payload -> {
try {
return producer.writeToKafka(payload, mapper);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return Uni.createFrom().<OutputSample>nothing();
});
}
Multi is intended to be used when you have a source that emits items continuously until it emits a completion event, which is not your case.
From Mutiny docs:
A Multi represents a stream of data. A stream can emit 0, 1, n, or an
infinite number of items.
You will rarely create instances of Multi yourself but instead use a
reactive client that exposes a Mutiny API.
What you are looking for is a Uni<List<OutputSample>> because your API you return 1 and only 1 item with the complete result list.
So what you need is to send each message to Kafka without immediately waiting for their return but collecting the generated Unis and then collecting it to a single Uni.
#POST
public Uni<List<OutputSample>> send(InputSample inputSample) {
// This could be injected directly inside your producer
ObjectMapper mapper = new ObjectMapper();
// Send each item to Kafka and collect resulting Unis
List<Uni<OutputSample>> uniList = deflateMessage(inputSample).stream()
.map(input -> producer.writeToKafka(input, mapper))
.collect(Collectors.toList());
// Transform a list of Unis to a single Uni of a list
#SuppressWarnings("unchecked") // Mutiny API fault...
Uni<List<OutputSample>> result = Uni.combine().all().unis(uniList)
.combinedWith(list -> (List<OutputSample>) list);
return result;
}
Basically what I am trying to achieve is to call a second repository (a ReactiveCrudRepository) or throw an exception, depending on the result of a call to a first repository.
My original idea looks like this:
/** Reactive with blocking code */
public Flux<SecondThing> getThings(String firstThingName) {
FirstThing firstThing = firstRepo
.findByName(firstThingName)
// Warning: "Inappropriate blocking method call"
.blockOptional() // this fails in test-context
.orElseThrow(() -> new FirstThingNotFound(firstThingName));
return secondRepo.findAllByFirstThingId(firstThing.getId());
}
Which would correspond to the following non-reactive approach:
/** Non-reactive */
public List<SecondThing> getThings(String firstThingName) {
FirstThing firstThing = firstRepo
.findByName(firstThingName)
.orElseThrow(() -> new FirstThingNotFound(firstThingName));
return secondRepo.findAllByFirstThingId(firstThing.getId());
}
I haven't found a way to do this in a reactive non-blocking way. All I need is to throw an error if an empty Mono comes out of the first call, and continue the pipeline if not empty; but I could not seem to use onErrorStop or doOnError correctly here, and map does not help as it skips the empty Mono.
What I have is a workaround if I use id instead of name, but I'm not quite satisfied with it as it shows a different behaviour in the case where is an instance of FirstThing but no SecondThing linked to it:
/** Reactive workaround 1 */
public Flux<SecondThing> getThings(Long firstThingId) {
return secondRepo
.findAllByFirstThingId(firstThingId)
.switchIfEmpty(
Flux.error(() -> new FirstThingNotFound(firstThingName))
);
}
Another workaround I've found is the following, that replaces the empty Mono with a null value, but it doesn't look right and throws a warning too:
/** Reactive workaround 2 */
public Flux<SecondThing> getThings(String firstThingName) {
return firstRepo
.findByName(firstThingName)
// Warning: "Passing 'null' argument to parameter annotated as #NotNull"
.defaultIfEmpty(null)
.flatMapMany(
firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
)
.onErrorMap(
NullPointerException.class, e -> new FirstThingNotFound(firstThingName)
);
}
What is the correct way to chain the calls to both repositories so that the presence or absence of a FirstThing with the requested firstThingName conditions the call to the second repo?
I found a solution so simple, that I could be ashamed not to have found it earlier:
public Flux<SecondThing> getThings(String firstThingName) {
return firstRepo
.findByName(firstThingName)
.switchIfEmpty(Mono.error(() -> new FirstThingNotFound(firstThingName)))
.flatMapMany(
firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
);
}
The trick is that switchIfEmpty does not force you to pick a "valid" replacement value, so it is possible to use a Mono.error to propagate the right exception directly.
I am trying to fetch the data from another microservice. Suppose you have three microservices: State, School and Student. You get the Flux< School> via stateId from SchoolRepository and for each School you are calling Student microservice via webclient which returns Flux< Students> and setting it to Flux< School>. Shortly, what I am trying to do is:
public Flux<School> getBySchool(Long stateId){
Flux<School> schoolList=schoolRepository.findByStateId(stateId);
//And for each school I want to do this
Flux<Student> studentsfound=webClient.get().uri("bla bla bla"+school.getSchoolId).exchange().flatMapMany(response->response.bodyToFlux(Student.class));
//I have a List<Student> entity in School domain, so I want Flux<Student> --> List<Student> and add it to School. Something like school.setStudentList(studentListReturned).
//And then return Flux<Stundent>
}
How can I iterate through Flux< School > , and after getting Flux< Student > how can I add it to the appropriate Flux< School> ? Thank you in advance.
UPDATE
SOLUTION
Many thanks to #K.Nicholas. I was able to solve the problem as below, but more elegant solutions are welcome. And I'm subscribing to schoolList in my controller as I have to return Flux< School> from service layer to controller layer.
public Flux<School> getBySchoolWithStudents(Long stateId) {
Flux<School> schoolList = schoolRepository.findByStateId(stateId);
return schoolList.flatMap(school -> {
Flux<Student> studentFlux = webClientBuilder.build().get().uri(REQUEST_URI + school.getSchoolId()).exchange().flatMapMany(response -> response.bodyToFlux(Student.class));
return studentFlux.collectList().map(list -> {
school.setStudentList(list);
return school;
});
});
}
EDIT: Second attempt. So, nothing particularly special that I can see. Uses the collectList method and assigns it in a map function. The map function returns the school object that is in scope. I had to do a bit of debugging to ensure my classes supported serialization/deserialization properly.
WebClient.create().get().uri(URI.create("http://localhost:8082/ss/school?state=CA"))
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(cr->cr.bodyToFlux(School.class))
.flatMap(school->{
return WebClient.create().get().uri(URI.create("http://localhost:8081/ss/student?school="+school.getName()))
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(crs->crs.bodyToFlux(Student.class))
.collectList()
.map(sl->{
school.setStudents(sl);
return school;
});
})
.subscribe(System.out::println);
I am trying to use project reactor mergeWith operator in order to achieve a if/elseif/else branching logic as described here: RxJS, where is the If-Else Operator.
The provided samples are written in RxJS but the underlying idea remains the same.
Basically the idea is to use the filter operator on 3 monos/publishers (therefore with 3 different predicates) and merge the 3 monos as follows (here they are RxJS Observables of course):
const somethings$ = source$
.filter(isSomething)
.do(something);
const betterThings$ = source$
.filter(isBetterThings)
.do(betterThings);
const defaultThings$ = source$
.filter((val) => !isSomething(val) && !isBetterThings(val))
.do(defaultThing);
// merge them together
const onlyTheRightThings$ = somethings$
.merge(
betterThings$,
defaultThings$,
)
.do(correctThings);
I have copied and pasted the relevant sample from the above article.
Consider that something$, betterThings$ and defaultThings$ are our monos isSomething & isBetterThings are the predicates.
Now here are my 3 real monos/publishers (written in java):
private Mono<ServerResponse> validateUser(User user) {
return Mono.just(new BeanPropertyBindingResult(user, User.class.getName()))
.doOnNext(err -> userValidator.validate(user, err))
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private Mono<ServerResponse> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(User user) {
return userRepository.save(user)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
Here is the top level method that needs to merge the three publishers:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.mergeWith(...)
}
I am not sure how to use the mergeWith() operator... I have tried the Mono.when() static operator which takes several publishers (good for me) but returns a Mono<void> (bad for me).
Can anyone please help?
P.S. I am sure you will excuse the mix between RxJS (js) and Reactor code (java). I meant to use my knowledge from RxJS in order to achieve a similar goal in my Reactor app. :-)
edit 1: I have tried this:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user))).single();
}
But I get this error: NoSuchElementException: Source was empty
edit 2: Same with (notice the parenthesis):
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single());
}
edit 3: Same error with a Mono<User>:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
Mono<User> userMono = serverRequest.bodyToMono(User.class);
return validateUser(userMono)
.or(validateEmailNotExists(userMono))
.or(saveUser(userMono))
.single();
}
edit 4: I can confirm that at least one of the three monos will always emit. It is when I use the or() operator that something goes wrong...
If I use this, all my tests pass:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(user -> Flux.concat(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single());
}
I have used the concat() operator here to preserve the order of operations.
Do you know what I am getting wrong with the or() operator?
edit 5: I have tried with the cache() operator as follows to no avail:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.cache()
.flatMap(user -> validateUser(user)
.or(validateEmailNotExists(user))
.or(saveUser(user))
.single()
);
}
Your current code sample implies that your 3 methods returning Mono<ServerResponse> should be taking a Mono<User> rather than a User, so you may need to alter something there.
However, I digress - that doesn't seem to be the main question here.
From what I understand of the pattern described in that link, you're creating 3 separate Mono objects, only one of which will ever return a result - and you need a Mono of whichever one of your original 3 Mono objects returns.
In that case, I'd recommend something like the following:
Mono<ServerResult> result = Flux.merge(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single();
Breaking it down:
The static Flux.merge() method takes your 3 Mono objects and merges them into a Flux;
next() returns the first available result as a Mono;
single() will ensure that the Mono emits a value, as oppose to nothing at all, and throw an exception otherwise. (Optional, but just a bit of a safety net.)
You could also just chain Mono.or() like so:
Mono<ServerResult> result = validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single();
The advantages to this approach are:
It's arguably more readable in some cases;
If it's possible that you'll have more than one Mono in your chain return a result, this allows you to set an order of precedence for which one is chosen (as oppose to the above example where you'll just get whatever Mono emitted a value first.)
The disadvantage is potentially one of performance. If saveUser() returns a value first in the above code, then you still have to wait for the other two Mono objects to complete before your combined Mono will complete.