For example, let's say I have a WebFilter that writes some Context
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(Context.of("my-context", "foobar"));
}
Downstream, my controller does this
#GetMapping(path = "test")
public Mono<String> test() throws Exception {
final Mono<ContextView> contextMono = Mono.deferContextual(Mono::just);
return contextMono.flatMap(ctx -> Mono.just(ctx.get("my-context")));
}
The above all works fine.
What if I wanted to return a Single from the controller method? I tried using RxJava3Adapter.monoToSingle() but it breaks the reactor chain.
#GetMapping(path = "test")
public Single<String> test() throws Exception {
final Mono<ContextView> contextMono = Mono.deferContextual(Mono::just);
return RxJava3Adapter.monoToSingle(
contextMono.flatMap(ctx -> Mono.just(ctx.get("my-context"))));
}
My guess is that since I'm not returning the Mono, nothing subscribes to this contextMono inside of the RxJava3Adapter. Is that the right explanation?
Is there any way to return a Single while having the Context be passed in?
The subscription itself works fine. The problem is that Context is a Reactor specific feature which is not part of the Reactive Streams standard. So when you convert a Mono to Single, the Context is lost.
In the code you attached you should just simply omit the Rx part to make it work but I imagine that your real world use case might be more convoluted. A good approach can be to convert the Rx code to Reactor at the earliest possible place (e.g. when you call the third-party library which returns the Rx type) and use Reactor in the rest of the codebase including the controller return type.
Related
I'm trying to understand what is the right way of implementing post_construct methods in Spring Webflux.
On startup of an application I need to read data from the DB (I have an R2dbcRepository configured), and then perform some logic and save result as Bean's fields).
So I have a findAll() method returning Flux. How should it be done?
I tried using .block(), AtomicBoolean flag, none of these worked
First of all, never use block() method. Use it for tests at most, but there is a better solution out there than StepVerifier. (If you use Kotlin there are await prefixed methods that work like block but not blocking.)
If you need data at launch, that says it is bad design to me because if there is no user, what do you do with it? I think it's illogical. What happens when you use query when you need it, add to cache and reuse it when you need it again. In the case of WebFlux, you can prepare a Mono object that uses a query from the database and use .cache() end of chain. So Spring Bean can contain this Mono object that will be run when you subscribe.
Ofc below example, repo.find will never call if function of Service won't run.
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#cache--
#Configuration
public class Config {
private R2dbcRepository repo;
public Config(R2dbcRepository repo) {
this.repo = repo;
}
#Bean
public Mono<Data> myCachedDbData() {
return repo.find(...)
.map(it -> new Data(it))
.cache()
}
}
#Service
public class Service {
private Mono<Data> data;
public Config(Mono<Data> data) {
this.data = data;
}
public Object function() {
return data.flatMap(...)
}
}
I'm a new for a project reactor, but i have task to send some information from classic spring rest controller to some service, which is interacts with different system. Whole project developed with project reactor.
Here is my rest controller:
#RestController
public class Controller {
#Autowired
Service service;
#PostMapping("/path")
public Mono<String> test(#RequestHeader Map<String, String> headers) throws Exception {
testService.saveHeader(headers.get("header"));
return service.getData();
}
And here is my service:
#Service
public class Service {
private Mono<String> monoHeader;
private InteractionService interactor;
public Mono<String> getData() {
return Mono.fromSupplier(() -> interactor.interact(monoHeader.block()));
}
public void saveHeader(String header) {
String key = "header";
monoHeader = Mono.just("")
.flatMap( s -> Mono.subscriberContext()
.map( ctx -> s + ctx.get(key)))
.subscriberContext(ctx -> ctx.put(key, header));
}
Is it acceptable solution?
Fisrt off, I don't think you need the Context here. It is useful to implicitly pass data to a Flux or a Mono that you don't create (eg. one that a database driver creates for you). But here you're in charge of creating the Mono<String>.
Does the service saveHeader really achieve something? The call seem transient in nature: you always immediately call the interactor with the last saved header. (there could be a side effect there where two parallel calls to your endpoint end up overwriting each other's headers).
If you really want to store the headers, you could add a list or map in your service, but the most logical path would be to add the header as a parameter of getData().
This eliminates monoHeader field and saveHeader method.
Then getData itself: you don't need to ever block() on a Mono if you aim at returning a Mono. Adding an input parameter would allow you to rewrite the method as:
public Mono<String> getData(String header) {
return Mono.fromSupplier(() -> interactor.interact(header));
}
Last but not least, blocking.
The interactor seems to be an external service or library that is not reactive in nature. If the operation involves some latency (which it probably does) or blocks for more than a few milliseconds, then it should run on a separate thread.
Mono.fromSupplier runs in whatever thread is subscribing to it. In this case, Spring WebFlux will subscribe to it, and it will run in the Netty eventloop thread. If you block that thread, it means no other request can be serviced in the whole application!
So you want to execute the interactor in a dedicated thread, which you can do by using subscribeOn(Schedulers.boundedElastic()).
All in all:
#RestController
public class Controller {
#Autowired
Service service;
#PostMapping("/path")
public Mono<String> test(#RequestHeader Map<String, String> headers) throws Exception {
return service.getData(headers.get("header"));
}
}
#Service
public class Service {
private InteractionService interactor;
public Mono<String> getData(String header) {
return Mono.fromSupplier(() -> interactor.interact(header))
.subscribeOn(Schedulers.boundedElastic());
}
}
How to transfer data via reactor's subscriber context?
Is it acceptable solution?
No.
Your code of saveHeader() method is an equivalent of simple
public void saveHeader(String header) {
monoHeader = Mono.just(header);
}
A subscriberContext is needed if you consume the value elsewhere - if the mono is constructed elsewhere. In your case (where you have all code before your eyes in the same method) just use the actual value.
BTW, there are many ways to implement your getData() method.
One is as suggested by Simon Baslé to get rid of a separate saveHeader() method.
One other way, if you have to keep your monoHeader field, could be
public Mono<String> getData() {
return monoHeader.publishOn(Schedulers.boundedElastic())
.map(header -> interactor.interact(header));
}
I need to rewrite some spring code to reactive-spring. So there's a reactive spring repository service which provides on type of object. But the return type of mapping method is another one type, which depends on locale. Now there's a blocking algorithm which calls get method from repository then split result, then take String locale and then make from them all one response object. How can I combine it into one non-blocking chain of reactive methods?
I see that the only thing I have to wait is Mongo Db service which is kind of reactive, but I want to write code properly to make Spring framework block Mono itself. Here is the example code:
#RestController
#RequestMapping
public class PhoneCheckController {
#Autowired
private MessageLocalization loc;
#Autowired
private PhoneCheckService service;
#GetMapping("/check")
public PhoneCheckResponse checkPhone(#Valid #ModelAttribute PhoneCheckRequest request, Locale locale) {
PhoneCheckResult result = service.checkPhoneNumber(DtoTransformer.toParams(request)).block();
return new PhoneCheckResponse(
result.getViolationLevel(),
loc.getMessage(result.getMessage(), locale)
);
}
}
I tried to make like this
public Mono<PhoneCheckResponse> checkPhone(#Valid #ModelAttribute PhoneCheckRequest request, Locale locale) {
PhoneCheckResult result = service.checkPhoneNumber(DtoTransformer.toParams(request));
return Mono.just(new PhoneCheckResponse(
result.getViolationLevel(), //reactive part I need put
loc.getMessage(result.getMessage(), locale) //reactive part
)
);
}
I want to call the method when previous returned Mono<Void>:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(saveNotificationLog(emailDto)); //it's not work
}
private void saveNotificationLog(EmailDto emailDto) {
notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Method sendEmailreturns Mono<Void>.
So how to call saveNotificationLog?
UPD: Tring to make my question simplier:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(System.out.print("Hello world!");
}
How to call doOnNextor similar method after sendEmail return Mono<Void>?
The Mono will not emit data, so doOnNext will not be triggered. You should use the doOnSuccess instead.
Also, your Mono need to be consumed. Without the code, we don't know if it is or not.
Some example here: I added subscribe() to consume the mono. Depending on the use of your Mono, you will have to do or not the same thing.
This print nothing:
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnNext(x->System.out.println("OK")).subscribe();
This print "OK":
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnSuccess(x->System.out.println("OK")).subscribe();
doOnNext, and in general all doOn* reactor methods are side-effect methods. You're not supposed to call them to do I/O work or chain operations, but rather log things and not do anything that would affect the state of the application.
In your code sample, notificationLogReactiveRepository.save returns Mono<Void>. The saveNotificationLog returns void and does not subscribe to the publisher returned by notificationLogReactiveRepository.save. This means the notification will not be saved, because nothing happens until you subscribe.
In this case, it seems you're trying to chain operations - then operators are just made for that. Your code should look like this:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.then(saveNotificationLog(emailDto));
}
private Mono<Void> saveNotificationLog(EmailDto emailDto) {
return notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Try it this way:
Mono.empty().then()
I'm trying to use JUnit4 in an Android project, where I also use RxAndroid/RxJava.
What I do is calling REST API from UUID generator using retrofit
UUIDApi.java just an interface for retrofit calls (now is just one)
public interface UUIDApi {
static final String BASE_URL = "https://www.uuidgenerator.net";
#GET("/api/version4")
Observable<String> getUUID();
}
UUIDModel.java where retrofit is initialized and where the interface written above is implemented
public class UUIDModel implements UUIDApi{
private Retrofit retrofit;
private UUIDApi uuidApi;
UUIDObserver uuidObserver = new UUIDObserver();
public UUIDModel() {
retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(new ToStringConverterFactory())
.baseUrl(UUIDApi.BASE_URL)
.build();
uuidApi = retrofit.create(UUIDApi.class);
}
#Override
public Observable<String> getUUID() {
return uuidApi.getUUID();
}
public void generateUUID(){
Observable<String> observable = this.getUUID();
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(uuidObserver);
}
}
Than I have the UUIDObserver that is just a class that implements Observer.
Note: new ToStringConverterFactory is a class I found here
Executing this code using emulator, I know for sure that it works fine. The problem is that I don't understand how to junit this code since using rxAndroid/rxJava it gets executed in another thread.
I read that:
The official way to test an observable is by using a TestSubscriber, an helper subscriber provided directly by the RxJava library.
so I tried
#Test
public void test_uuid() throws Exception {
UUIDApi uuidApi = new UUIDModel();
Observable<String> observable = uuidApi.getUUID();
TestSubscriber<String> testSubscriber = new TestSubscriber<>();
observable.subscribe(testSubscriber);
}
but at observable.subscribe(testSubscriber); I get the error 'cannot resolve method 'subscribe(io.reactivex.subscribers.TestSubscriber)'
What am I doing wrong? How should I cope with rxProgramming and JUnit?
TestSubscriber is relevant for Flowable while TestObserver is used with Observable. But for both you don't actually have to subscribe anything manually. You just use the test() method instead.
observable.test()
.assertNoErrors()
.assertValue(expected)
.assertComplete();
I am testing RxJava + Retrofit like that :
First of all I am using Schedulers.trampoline() for testing purposes.
Then in a basic unit test :
#Mock
ResponseBody mockResponseBody;
#Test
public void testCondition(){
when(mService.operation()).thenReturn(Observable.just(response));
mPresenter.handleOperation();
verify(mView).operationResult();
}
I am testing 3 case : Success response(between 200-300),error response,fail
For testing success : response = Response.success(your_body);
For testing error : response= Response.error(any_error_code,mockResponseBody);
For testing fail : Just return Observable.error(); inside thenReturn
In this unit test we are mocking our webservice calls and it never depends on webservice so you can run your unit tests even when you offline.
But if you want to see real webservice calls you can write integration tests.
You could utilize test operator to subscribe your Observable with TestObserver, which records events and allows you to make assertions about them:
observable
.test()
.assertValue(...)
.assertComplete()
//etc
To deal with async operations in your reactive stream you could try these approaches:
utilize TestObserver's handy await operator, wich awaits until the TestObserver receives an onError or onComplete events.
provide Schedulers via dependency injection (e.g. with constructor injection), so you could substitute them in tests with Schedulers.trampoline.
change your async Scheduler's default implementation with one of RxJava's hook functions (e.g. RxJavaPlugins.setInitIoSchedulerHandler).