1

Context: I created succesfully a method with "org.springframework.data.domain.Pageable" parameter aimed to return a Flux. I have only found articles guiding to return a Flux instead of Mono when Pageable is involded. So far so good.

Personal knowlodge/assumption: it does't make sense return a Flux if it is just a page. In other world, it is not a stream returning more than a single result. Well, if I could require from page 2 to 10 from 100 pages I would see some point on prohibiting to use Mono. As far as I understand, the real event from retrieveAllPaged bellow is either 0 event or 1 (never more than 1).

Here is the method working with Flux return:

public interface ModelRepository extends ReactiveCrudRepository<Extrato, String> {
    @Query("{ id: { $exists: true }}")
    Flux<Extrato> retrieveAllFluxPaged(final Pageable page);
}

Tentative: since it will never return more than 1 I tried:

public interface ExtratoRepository extends ReactiveCrudRepository<Extrato, String> {

    @Query("{ id: { $exists: true }}")
    Mono<Extrato> retrieveAllMonoPaged(final Pageable page);
}

And I got an exception which seems to advise that Mono isn't acceptable:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file .... Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono 

My issue and straight question: isn't really possible to return a Mono when pagining? If so, a relevant comment would be why if I call a second time from front-end it will be a complete new search? I mean, for instance, calling twice from RxJs would never cause the second call reuse the flux created by first call as far as I understand it.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable()
export class AppService {

  constructor(private http: HttpClient) {}

  getPage_1_and_2() {

    this.http
      .get<any[]>('http://localhost:8080/extrato/paged?page=1&size=1')
      .pipe(map(data => data));

    this.http
      .get<any[]>('http://localhost:8080/extrato/paged?page=2&size=1')
      .pipe(map(data => data));

  }

Complete Logs

2020-03-25 15:25:16.803  WARN 11624 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file [C:\WSs\webflux\demo\target\classes\com\noblockingcase\demo\configuration\TestDataLoader.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'extratoRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono com.noblockingcase.demo.repository.ExtratoRepository.retrieveAllExtratosPaged(org.springframework.data.domain.Pageable)
2020-03-25 15:25:19.581  INFO 11624 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-03-25 15:25:19.587 ERROR 11624 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file [C:\WSs\webflux\demo\target\classes\com\noblockingcase\demo\configuration\TestDataLoader.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'extratoRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono com.noblockingcase.demo.repository.ExtratoRepository.retrieveAllExtratosPaged(org.springframework.data.domain.Pageable)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1358) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at com.noblockingcase.demo.DemoApplication.main(DemoApplication.java:12) ~[classes/:na]
Jim C
  • 3,957
  • 25
  • 85
  • 162

1 Answers1

3

EDIT: Clarify the interaction.

When you request a "page" of size 10, on the server you'll get a Flux from the repo that will emit 10 items then complete. Because Angular expects 1 response Spring will collect all 10 elements in the Controller (non-blocking) into a collection and ship them out when the Flux completes. The client Observable will emit 1 value (an array), not 10 discreet items. You'll notice this in your Angular payload:

this.http
      .get<any[]>

Because of this a Flux is fine as Spring can use content negotiation to do the right thing.

If you really want to go to a Mono you likely want .collectList(). This will change your Flux to a Mono of a List.

Mono<List<String>> listMono = Flux.just("foo", "bar", "baz").collectList();

If you know your Flux is really only going to be 1 value you can call .single() on it. Just be aware if you produce 0 or > 1 element it will error.

Mono<String> foo = Flux.just("foo").single();
Adam Bickford
  • 1,216
  • 12
  • 14
  • Are you sure about "A Flux isn't really useful for a front end like Angular because by default it is expecting a request -> response in order to parse well formed json"? I have done numerous codes where an RxJs Obrserver is expecting a flux as response. Did I messed somethng? There are many examples around with Angular/WebFlux. Well, many Hello Worlds POC don't mean much but I was quite confident that returning a flux to front-end would be a good practice mainly when working with no-blocking/reactive approaches. Please, add more details to your statement or point some article – Jim C Apr 02 '20 at 01:41
  • 1
    Sorry, I wasn't clear on that. I meant you're not going to get 'streaming' behavior from Angular like you would with a reactive client. Spring will batch it up into a json array based on content negotiation. – Adam Bickford Apr 02 '20 at 02:44
  • Thanks. You answered my question. – Jim C Apr 02 '20 at 13:38