I have the following request handler
fun x(req: ServerRequest) = req.toMono()
.flatMap {
...
val oldest = myRepository.findOldest(...) // this is the object I want to modify
...
val v= anotherMongoReactiveRepository.save(Y(...)) // this saves successfully
myRepository.save(oldest.copy(
remaining = (oldest.remaining - 1)
)) // this is not saved
ok().body(...)
}
and the following mongodb reactive repository
#Repository
interface MyRepository : ReactiveMongoRepository<X, String>, ... {
}
The problem is that after the save() method is executed there is no changed in the object. I managed to fix the problem with save().block() but I don't know why the first save on the other repository works and this one isn't. Why is this block() required?
Nothing happens until someone subscribes to reactive Publisher. That's why it started to work when you used block(). If you need to make a call to DB and use the result in another DB request than use Mono / Flux operators like map(), flatMap(),... to build a pipeline of all the operations you need and after that return resulting Mono / Flux as controller’s response. Spring will subscribe to that Mono / Flux and will return the request. You don't need to block it. And it is not recommended to do it (to use block() method).
Short example how to work with MongoDB reactive repositories in Java:
#GetMapping("/users")
public Mono<User> getPopulation() {
return userRepository.findOldest()
.flatMap(user -> { // process the response from DB
user.setTheOldest(true);
return userRepository.save(user);
})
.map(user -> {...}); // another processing
}
Related
I'm trying to write a simple algorithm: if no value in db then go to a remote source for it.
I tried do it this way:
public Mono<TicketData> getTicketData(Mono<TicketDataRequest> request) {
return request.flatMap(it ->
ticketDataRepository.findById(it.toRequestKey())
.defaultIfEmpty(getNewTicketData(it))
);
}
However, it does not work at all. In debug mode I checked a record in db with block() - it is present, but it all the time call getNewTicketData(it) in defaultIfEmpty. Use block() for solve it with traditional way I can not do, because it is throws exception:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
Which way I can do so simplest operations like get data from another stream if current is unsatisfied?
public Mono<TicketData> getTicketData(Mono<TicketDataRequest> request) {
return request.flatMap(it ->
ticketDataRepository.findById(it.toRequestKey())
.switchIfEmpty(Mono.just(getNewTicketData(it)))
);
}
Does the same as defaultIfEmpty.
Repo:
#Repository
public interface TicketDataRepository extends ReactiveCrudRepository<TicketData, String> {
}
Method getNewTicketData just returns POJO TicketData.
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'm using a RouterFunction to define endpoints in my Spring Boot application. My service returns a Mono<Object> and I want to return the result of this when the endpoint is called. I also need to authenticate so I pass a UserPrinciple object through.
Router
#Bean
RouterFunction<ServerResponse> router() {
return route()
.GET("/api/endpoint-name", this::getExample)
.build();
}
private Mono<ServerResponse> getExample(ServerRequest request) {
return ServerResponse.ok().body(fromPublisher(getUserPrincipal().map(service::getSomething), Object.class)).log();
}
private Mono<UserPrincipal> getUserPrincipal() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.map(UserPrincipal.class::cast);
}
Service
public Mono<Object> getSomething(UserPrincipal userPrincipal) {
WebClient webClient = getWebClient(userPrincipal.getJwt());
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("another/server/endpoint").build())
.retrieve()
.bodyToMono(Object.class);
}
The endpoint is returning this:
{
"scanAvailable": true
}
which suggests that I'm passing the Mono into the body of the response instead of passing in the result. However I've used fromPublisher which I thought would resolve this.
I can't find any examples where the service returns a Mono and the route correctly returns the result of the Mono.
How can I correctly pass a Mono/Flux as the body of the response?
im not going to explain the difference between mapand flatMapsince i have already written a quite comprehensive explanation here:
Do you have a test to show differences between the reactor map() and flatMap()?
The problem in the above code is the return of Object. And input parameters of Object into certain functions. The first function is pretty straight forward
Mono<UserPrincipal> = getUserPrincipal();
While the second one gets a bit more hairy:
Mono<Mono<Object> value = getUserPrincipal().map(service::getSomething);
So why are we getting A nested Mono?, well the get something returns a Mono<Object> and the Map return according the the api is Mono<R> where R is what we return from getSomething.
We then stick it into the fromPublisher which will unrap the first Mono ending up trying to serialize the Mono<Object>resulting in the strange response.
{
"scanAvailable": true
}
The answer here is pay more close attention to the type system. The body function takes a Publisher (Mono or Flux) so you don't need the fromPublisher function.
And also changing map to flatMap since the return type from inside a flatMap is a publisher.
ServerResponse.ok()
.body(getUserPrincipal()
.flatMap(service::getSomething), Object.class));
So I'm a beginner with RxJava but here's what I want to accomplish:
MainViewModel talks to the Repository. Repository has both LocalDataStore (that talks with the database) and RemoteDataStore (Retrofit) Both are different implementations of interface DataStore).
What I want to achieve is have a single call fetchData from the Repository that returns an Observable but:
it takes it from the RemoteDataStore at first
after fetching every single thing (onNext()), it inserts it into the database
if it fails, it returns results from the LocalDataStore.
However, I don't know how to implement this logic. Subscription happens on the ViewModel's end, but I cannot really change the observable to LocalDataStore from Repository end (?). Upserting data into the database also returns an Observable (Single to be precise) and for it to work it needs a subscription.
Could someone explain it to me or point me in a good direction?
My code (problem in repository comments):
Remote data store
override fun getData(): Observable<SomeData> = api
.getData(token)
.flatMapIterable { x -> x }
Local data store
override fun saveData(data: SomeData): Single<SomeData> {
return database.upsert(data)
}
Repository
fun getData(): Observable<SomeData> {
return
remoteDataStore.getData()
.doOnError {
localDataStore.getData() //? this will invoke but nothing happens because I'm not subscribed to it
}
.doOnNext {
saveData(it) //The same as before, nothing will happen
}
}
ViewModel
override fun fetchData() {
repository.getData()
.observeOn(androidScheduler)
.subscribeOn(threadScheduler)
.subscribe(
{ data: SomeData ->
dataList.add(data)
},
{ throwable: Throwable? ->
handleError(throwable)
},
{
//send data to view
},
{ disposable: Disposable ->
compositeDisposable.add(disposable)
}
)
}
Thank you for your time.
You need to use one of onErrorResumeNext methods. I would also suggest to change your stream type from Observable to Single as nature of your data seems like Get data once or throw error. It's just a good API design.
In your particular case I would implement the repository this way:
class RepositoryImpl #Inject constructor(private val localRepository: Repository, private val remoteRepository: Repository) : Repository {
override fun getData(): Single<Data> = remoteRepository.getData()
.onErrorResumeNext { throwable ->
if (throwable is IOException) {
return localRepository.getData()
}
return Single.error(throwable)
}
}
You might ask why only catch IOException? I usually handle only this exception to not miss anything critical but only unimportant network errors. If you will catch every exception you might miss, for example, a NullPointerException.
onErrorResumeNext is what you're looking for. doOnError invokes a side-effecting action, doesn't replace the original Observable with another one.
I have a reactive rest api (webflux), also using the spring WebClient class, to request data from other rest services.
Simplified design:
#PostMapping(value = "/document")
public Mono<Document> save(#RequestBody Mono<Document> document){
//1st Problem: I do not know how to get the documentoOwner ID
//that is inside the Document class from the request body without using .block()
Mono<DocumentOwner> documentOwner = documentOwnerWebClient()
.get().uri("/document-owner/{id}", document.getDocumentOwner().getId())
.accept(MediaType.APPLICATION_STREAM_JSON)
.exchange()
.flatMap(do -> do.bodyToMono(DocumentOwner.class));
//2nd Problem: I need to check (validate) if the documentOwner object is "active", for instance
//documentOwner and document instances below should be the object per se, not the Mono returned from the external API
if (!documentOwner.isActive) throw SomeBusinessException();
document.setDocumentOwner(documentOwner);
//Now I can save the document in some reactive repository,
//and return the one saved with the given ID.
return documentRepository.save(document)
}
In other words: I understand (almost) all of the reactive examples individually, but I am not able to put it all together and build a simple use case (get -> validate -> save -> return) without blocking the objects.
The closer I could get is:
#PostMapping(value = "/document")
public Mono<Document> salvar(#RequestBody Mono<Document> documentRequest){
return documentRequest
.transform(this::getDocumentOwner)
.transform(this::validateDocumentOwner)
.and(documentRequest, this::setDocumentOwner)
.transform(this::saveDocument);
}
Auxiliar methods are:
private Mono<DocumentOwner> getDocumentOwner(Mono<Document> document) {
return document.flatMap(p -> documentOwnerConsumer.getDocumentOwner(p.getDocumentOwnerId()));
}
private Mono<DocumentOwner> validateDocumentOwner(Mono<DocumentOwner> documentOwner) {
return documentOwner.flatMap(do -> {
if (do.getActive()) {
return Mono.error(new BusinessException("Document Owner is Inactive"));
}
return Mono.just(do);
});
}
private DocumentOwnersetDocumentOwner(DocumentOwner documentOwner, Document document) {
document.setDocumentOwner(documentOwner);
return document;
}
private Mono<Document> saveDocument(Mono<Document> documentMono) {
return documentMono.flatMap(documentRepository::save);
}
I am using Netty, SpringBoot, Spring WebFlux and Reactive Mongo Repository. But there are some problems:
1) I am getting the error:
java.lang.IllegalStateException: Only one connection receive subscriber allowed. Maybe because I am using the same documentRequest to transform and to setDocumentOwner. I really don't know.
2) setDocumentOwner method is not being called. So the document object to be saved is not updated. I believe could have a better way to implement this setDocumentOwner().
Thanks
I don't really get the point with the validation aspect of that question.
But it looks like you're trying to compose reactive types together. This is something that reactor handles perfectly with operators.
There are certainly better ways to handler that case, but a quick search in the Mono API makes me think about this:
Mono<Document> document = ...
Mono<DocumentOwner> docOwner = ...
another = Mono.when(document, docOwner)
.map(tuple -> {
Document doc = tuple.getT1();
DocumentOwner owner = tuple.getT2();
if(owner.getActive()) {
return Mono.error(new BusinessException("Document Owner is Inactive"));
}
doc.setDocumentOwner(owner);
return doc;
})
.flatMap(documentRepository::save);