I'm thinking how to use RXJava for the scenario described bellow.
A List<Object>,each object will be sent to k8s and checked the status till the respone return true,so my polling active is that:
private Observable<Boolean> startPolling(String content) {
log.info("start polling "+ content);
return Observable.interval(2, TimeUnit.SECONDS)
.take(3)
.observeOn(Schedulers.newThread())
.flatMap(aLong -> Observable.just(new CheckSvcStatus().check(content)))
.takeUntil(checkResult -> checkResult)
.timeout(3000L, TimeUnit.MILLISECONDS, Observable.just(false))
;
}
Function of sent action:
Observable<Compo> sentYamlAndGet() {
log.info("sent yaml");
sentYaml()
return Observable.just(content);
}
I try to use the foreach to get each object status which like this:
public void rxInstall() throws JsonProcessingException {
List<Boolean>observables = Lists.newArrayList();
Observable.from(list)
.subscribeOn(Schedulers.newThread())
.concatMap(s -> sendYamlAndGet())
.timeout(3000l, TimeUnit.MILLISECONDS)
.subscribe()
;
Observable.from(list).forEach(s -> {
observables.add(Observable.just(s)
.flatMap(this::startPolling)
.toBlocking()
.last()
)
;
System.out.println(new ObjectMapper().writeValueAsString(observables));
}
Objects of outputs list is :{"o1","o2","o3","o4","o5"}
the last status of objest which I want is : [false,true,false,false,true].
All above style is not much 'ReactX',check object status action do not affect to each other.
How to throw foreach? I trid toIterable(),toList() but failed.
Observable.from(list)
.concatMap(s -> sentYamlAndGet())
.concatMap(this::startPolling)
....
;
Wanted to know if it's good practice to do that and what would be the best way to do that?
Thanks in advance.
pps: currentlly I'm using rxjava1 <version>1.2.0</version> but can change to 2(´▽`)ノ
Related
I have the following unit test (JUnit 5):
FluxExchangeResult<CalendarDTO> calendarEntityResult = client.get()
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.returnResult(CalendarDTO.class);
assertNotNull(calendarEntityResult);
final Flux<CalendarDTO> responseBody = calendarEntityResult.getResponseBody();
responseBody.flatMap(calendarDTO -> {
assertNotNull(calendarDTO);
final List<AppointmentDTO> calendarEvents = calendarDTO.getCalendarEvents();
assertNotNull(calendarEvents);
assertFalse(calendarEvents.isEmpty());
return Flux.just(calendarEvents);
}).map(calendarEvents ->
calendarEvents.get(0)
).doOnNext(appointmentDTO ->
assertEquals(appointmentDTO, validAppointmentDTO())
).subscribe();
/*StepVerifier.create(responseBody)
.assertNext(calendarDTO -> {
assertNotNull(calendarDTO);
final List<AppointmentDTO> calendarEvents = calendarDTO.getCalendarEvents();
assertNotNull(calendarEvents);
assertFalse(calendarEvents.isEmpty());
final AppointmentDTO appointmentDTO = calendarEvents.get(0);
assertNotNull(appointmentDTO);
assertEquals(validAppointmentDTO(), appointmentDTO);
})
.expectComplete()
.verify();*/
For some reason, the assertNotNull(calendarEvents); is failing. The method itself when running it with Postman is fine. What has me puzzled is that on debug time, the calendarEntityResult has calendarEvents!
> GET /appointments
> WebTestClient-Request-Id: [1]
No content
< 200 OK OK
< Content-Type: [application/json;charset=UTF-8]
< Content-Length: [377]
{"data":{"calendarEvents":[{"id":null,"startTime":"2020-01-16T13:19:37.510-06:00","endTime":"2020-01-16T14:19:37.511-06:00","timeZoneStart":"America/Regina","timeZoneEnd":"America/Regina","summary":"unit test summary","description":"unit test description","organizerName":"Developer","organizerEmail":"developer#dev.com","status":null,"alarm":15}]},"notifications":null}
The commented code gives the same result. To be clear, the DTO itself is not null; the problem is the calendarEvents array. It's possible I'm doing something wrong since I'm new to reactive programming in general, so code improvements are most welcome. Am I extracting the data in a wrong manner?
You should be using the stepverifier when asserting any type of flux. Will make your life easier.
final Flux<String> responseBody = testClient.get()
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody();
StepVerifier.create(responseBody)
.assertNext(s -> assertEquals(s, "Foo"))
.assertNext(s -> assertEquals(s, "Bar"));
Turns out the root DTO was already wrapped into yet another DTO. It probably caused getResponseBody() to misinterpret the contents of the list and defaulted those to null. Leaving response here for the curious:
final Flux<AppointmentCalendarResponse> responseBody = client.get()
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.returnResult(AppointmentCalendarResponse.class)
.getResponseBody();
StepVerifier.create(responseBody)
.assertNext(data -> {
CalendarDTO calendarDTO = data.getData();
final List<AppointmentDTO> calendarEvents = calendarDTO.getCalendarEvents();
assertNotNull(calendarEvents);
})
.expectComplete()
.verify();
You're using the same DB?
Because generaly the DB test and DB Dev is different.
Consider a code:
WebClient webClient = ... ;
public Mono<MyWrapper> someFunction () {
Mono<MyDto> mono = webClient.get()
.uri("myUrl")
.retrieve()
.bodyToMono(MyDto.class);
Mono<FirstDto> first = mono.map(dto -> {...});
Mono<SecondDto> second = mono.map(dto -> {...}); //<- connection closed error here
return Mono.zip(first, second).map(zip -> {
return new MyWrapper(first, second);
});
}
Second map operation leads to connection closed error. I suppose that flux tried to send new request. (Does it or not?)
Second is there a way to map mono twice: to one type and another one without sending new request?
Have you tried using compose?
Mono<MyWrapper> mono = webClient.get()
.uri("myUrl")
.retrieve()
.bodyToMono(MyDto.class)
.compose(dto -> dto
.zip(dto.map(dto -> {...}), dto.map(dto -> {...})))
.map(MyWrapper::new);
API:
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#compose-java.util.function.Function-
I want to log exceptions while using VAVR (formerly javaslang). Below is the sample code snippet.
//will get 500 as response from this url
String sampleurl = "http://someurl.com";
List<String> myList = List.of(sampleurl);
LOGGER.info("In {} with urls {}",getClass(),myList);
return Observable.from(myList).flatMap(url ->
Observable.create(subscriber -> {
Try<String> httpEntity = HttpUtil.retrieveData(url).flatMap(httpResponse -> Try.of( () -> EntityUtils.toString(httpResponse.getEntity())));
httpEntity
.andThen(subscriber::onNext)
.andThen(subscriber::onCompleted)
.onFailure(subscriber::onError);
}));
I am trying to log exception in the onFailure() block but nothing gets logged. Please advise me on this.
Regards,
Jai
In both cases, success and failure, Vavr works as expected. Here are simple tests:
// prints nothing
Try.success("ok")
.andThen(() -> {})
.andThen(() -> {})
.onFailure(System.out::println);
// prints "java.lang.Error: ok"
Try.failure(new Error("ok"))
.andThen(() -> {})
.andThen(() -> {})
.onFailure(System.out::println);
I see two possible answers why the failure is not logged in your example:
the logger configuration does not fit your needs
the observables are not processed and Try is never called
Disclamer: I'm the creator of Vavr
I have a request method that use 'username' and 'password' body parameters to find real user from database, validate it by using request password and if everything is fine - return generated token as String.
public Mono<ServerResponse> handleLogin(ServerRequest request) {
User body = request.bodyToMono(User.class).block();
return userRepository.findById(body.getUsername())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
.flatMap(user -> ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class))
.switchIfEmpty(ServerResponse.badRequest().build());
}
This method works fine, but i'm trying to make it as non-blocking and i'm not sure how to achieve it. Any help appreciated.
Updated
For now i changed my method content to
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername()).flatMap(user -> Mono.just(new Object[]{body.getPassword(), user})))
.filter(obj -> {
User user = (User) obj[1];
return user.isActive() && passwordEncoder.matches((CharSequence) obj[0], user.getPassword());
})
.flatMap(obj -> {
User user = (User) obj[1];
return ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class);
})
.switchIfEmpty(ServerResponse.badRequest().build());
This is non-blocking, but looks like not so elegant solution. Can it be improved/simplified somehow?
Remember
1) Everything is stream.
2) Keep your flow composed
3) No blocking operation at all.
Solution
So, keep your flow composed without blocking operation
public Mono<ServerResponse> handleLogin(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
.flatMap(user -> ServerResponse.ok().body(Mono.just(tokens.store(user)), String.class))
.switchIfEmpty(ServerResponse.badRequest().build());
}
Regarding the last update
Regarding the last update, from my point of view, the code might be optimized in a next way:
return request.bodyToMono(User.class)
.flatMap(body -> userRepository.findById(body.getUsername())
.filter(user -> user.isActive())
.filter(user -> passwordEncoder.matches(body.getPassword(), user.getPassword()))
)
.flatMap(user -> ServerResponse.ok().syncBody(tokens.store(user)))
.switchIfEmpty(ServerResponse.badRequest().build());
I have a use case where I need to aggregate the finished thread responses from multiple Observable objects and return back to the client. My question is how to achieve it with using the rX Java. Here I have written a code snippet but the issue of this one is that this won't return anything after the timeout.
Observable<AggregateResponse> aggregateResponse = Observable.
zip(callServiceA(endpoint), callServiceB(endpoint), callServiceC(endpoint),
(Mashup resultA, Mashup resultB, Mashup resultC) -> {
AggregateResponse result = new AggregateResponse();
result.setResult(resultA.getName() + " " + resultB.getName() + " " + resultC.getName());
return result;
}).timeout(5, TimeUnit.SECONDS);
Subscriber
aggregateResponse.subscribe(new Subscriber<AggregateResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
//Timeout execute this rather than aggregating the finished tasks
System.out.println(throwable.getMessage());
System.out.println(throwable.getClass());
}
#Override
public void onNext(AggregateResponse response) {
asyncResponse.resume(response);
}
});
You need to put the timeout operator on each Observable, zip will wait for all Observables to emit a value before emitting a result, so if only one of them take longer while others already emitted, you will cut down the stream with the timeout (with onError) before the zipped Observable will have a chance to emit.
What you should do, assuming you want to ignore timed out sources while keeping the rest, is to add timeout operator to each Observable and also add error handling like onErrorReturn to each one, the error return can return some kind of 'empty' result (you can't use null in RxJava2), and when you aggregate result ignore those empty results:
Observable<AggregateResponse> aggregateResponse = Observable.
zip(callServiceA(endpoint)
.timeout(5, TimeUnit.SECONDS)
.onErrorReturn(throwable -> new Mashup()),
callServiceB(endpoint)
.timeout(5, TimeUnit.SECONDS)
.onErrorReturn(throwable -> new Mashup()),
callServiceC(endpoint)
.timeout(5, TimeUnit.SECONDS)
.onErrorReturn(throwable -> new Mashup()),
(Mashup resultA, Mashup resultB, Mashup resultC) -> {
AggregateResponse result = new AggregateResponse();
result.setResult(resultA.getName() + " " + resultB.getName() + " " + resultC.getName());
return result;
});