1

I'm actually confused on assembly time and subscription time. I know mono's are lazy and does not get executed until its subscribed. Below is a method.

    public Mono<UserbaseEntityResponse> getUserbaseDetailsForEntityId(String id) {
        GroupRequest request = ImmutableGroupRequest
                .builder()
                .cloudId(id)
                .build();

        Mono<List<GroupResponse>> response = ussClient.getGroups(request);
        List<UserbaseEntityResponse.GroupPrincipal> groups = new CopyOnWriteArrayList<>();


        response.flatMapIterable(elem -> elem)
                .toIterable().iterator().forEachRemaining(
                    groupResponse -> {
                        groupResponse.getResources().iterator().forEachRemaining(
                            resource -> {
                                groups.add(ImmutableGroupPrincipal
                                        .builder()
                                        .groupId(resource.getId())
                                        .name(resource.getDisplayName())
                                        .addAllUsers(convertMemebersToUsers(resource))
                                        .build());
                            }
                        );
                    }
                );
        log.debug("Response Object - " + groups.toString());
        ImmutableUserbaseEntityResponse res = ImmutableUserbaseEntityResponse
                .builder()
                .userbaseId(id)
                .addAllGroups(groups)
                .build();


        Flux<UserbaseEntityResponse.GroupPrincipal> f = Flux.fromIterable(res.getGroups())
        .parallel()
                .runOn(Schedulers.parallel())
                .doOnNext(groupPrincipal -> getResourcesForGroup((ImmutableGroupPrincipal)groupPrincipal, res.getUserbaseId()))
                .sequential();

        return Mono.just(res);
    }

This gets executed Mono<List<GroupResponse>> response = ussClient.getGroups(request); without calling subscribe, however below will not get executed unless I call subscribe on that.

Flux<UserbaseEntityResponse.GroupPrincipal> f = Flux.fromIterable(res.getGroups())
        .parallel()
                .runOn(Schedulers.parallel())
                .doOnNext(groupPrincipal -> getResourcesForGroup((ImmutableGroupPrincipal)groupPrincipal, res.getUserbaseId()))
                .sequential();

Can I get some more input on assembly time vs subscription?

Pramod Shashidhara
  • 939
  • 2
  • 11
  • 17

1 Answers1

2

"Nothing happens until you subscribe" isn't quite true in all cases. There's three scenarios in which a publisher (Mono or Flux) will be executed:

  • You subscribe;
  • You block;
  • The publisher is "hot".

Note that the above scenarios all apply to an entire reactive chain - i.e. if I subscribe to a publisher, everything upstream (dependent on that publisher) also executes. That's why frameworks can, and should call subscribe when they need to, causing the reactive chain defined in a controller to execute.

In your case it's actually the second of these - you're blocking, which is essentially a "subscribe and wait for the result(s)". Usually the methods that block are clearly labelled, but again that's not always the case - in your case it's the toIterable() method on Flux doing the blocking:

Transform this Flux into a lazy Iterable blocking on Iterator.next() calls.

But ah, you say, I'm not calling Iterator.next() - what gives?!

Well, implicitly you are by calling forEachRemaining():

The default implementation behaves as if:

 while (hasNext())
     action.accept(next());

...and as per the above rule, since ussClient.getGroups(request) is upstream of this blocking call, it gets executed.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • Nice, now here is another example. I'm wondering how is this getting executed. This has no blockers. This is handler for an API. ```Mono healthCheck(ServerRequest serverRequest) { jobService.putJob(any()); return ServerResponse.ok().contentType(APPLICATION_JSON).body(Mono.just("application is healthy"), String.class); } ``` – Pramod Shashidhara Jul 04 '20 at 00:12
  • In the above case, is it best to call subscribe on Flux or is there any better way of doing this? – Pramod Shashidhara Jul 04 '20 at 00:29
  • @PramodShashidhara That won't get executed until it's subscribed to, but assuming it's a method defined in a controller, then the framework (Webflux) will subscribe to it at the appropriate time (when someone makes a request.) – Michael Berry Jul 04 '20 at 13:24
  • Got it. 1. Is it advisable to use subscribe anywhere in the program? For.E.g in the above example Can I just use ```.sequential().subscribe();``` – Pramod Shashidhara Jul 04 '20 at 15:22
  • No - using subscribe is almost always a code smell. The subscribing should be left to the underlying framework. – Michael Berry Jul 04 '20 at 19:46
  • Thank You, and regarding the above controller example in comments, will the `jobService.putJob` gets executed during assembly time or subscription time? – Pramod Shashidhara Jul 05 '20 at 20:59
  • @PramodShashidhara Assembly time, since it's not part of the reactive chain. – Michael Berry Jul 05 '20 at 21:38
  • I added another method `userbaseMergeSqsService.write()` below `jobService.putJob` even this is not part of reactive chain. But `userbaseMergeSqsService.write` method doesnt get executed. `jobService` writes to DB and `userbaseMergeSqsService` writes to SQS. I'm confused, need your help. How do we build a pipeline when making network calls? – Pramod Shashidhara Jul 06 '20 at 15:43
  • `userbaseMergeSqsService.write()` will get *executed* if it's not part of the reactive chain, but assuming it's a blocking call that returns a publisher, simply executing that line won't do much. That publisher would need to be subscribed to for the SQS write to happen, and it won't get subscribed to unless it's part of the reactive chain. – Michael Berry Jul 06 '20 at 17:08