landbit
landbit

Reputation: 111

spring webflux how to manage sequential business logic code in reactive world

Is this approach is reactive friendly?

I have a reactive controller "save" method calling myService.save(request).

The service layer needs to:

  1. jdbc save(on another scheduler because code is blocking),
  2. generate a template string (on another scheduler),
  3. send an email(on another scheduler),
  4. finally return the saved entity to the controller layer

I can't chain all my calls in one pipeline or I don't know how to achieve this, because I want to send back (1) that is lost as soon as I do ....flatMap(templateService::generateStringTemplate) for example.

So instead I trigger my sub operations inside (1).

Is it how am I supposed to handle this or is there a clever way to do it in one pipeline ?

Below code to support the question. Thanks.

Service called by Controller layer

    public Mono<Prospect> save(final Prospect prospect) {

    return Mono.fromCallable(
            () -> {
                Prospect savedProspect = transactionTemplate.execute(status -> prospectRepository.save(prospect));

                templateService.generateProspectSubscription(savedProspect)
                        .map(t ->
                                EmailPostRequest.builder()
                                       ...
                                        .build())
                        .flatMap(emailService::send)
                        .subscribe();

                return savedProspect;
            })
            .subscribeOn(jdbcScheduler);

}

TemplateService

public Mono<String> generateProspectSubscription(final Prospect prospect) {        
    return Mono.fromCallable(
            () -> {
                Map<String, Object> model = new HashMap<>();
               ...

                Template t = freemarkerConfig.getTemplate(WELCOME_EN_FTL);
                String html = FreeMarkerTemplateUtils.processTemplateIntoString(t, model);
                return html;
            }
    ).subscribeOn(freemarkerScheduler);
}

EmailService

 public Mono<Void> send(final EmailPostRequest e) {

    return Mono.fromCallable(
            () -> {
                MimeMessage message = emailSender.createMimeMessage();
                MimeMessageHelper mimeHelper = new MimeMessageHelper(message,
                        MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED,
                        StandardCharsets.UTF_8.name());


                mimeHelper.setTo(e.getTo());
                mimeHelper.setText(e.getText(), true);
                mimeHelper.setSubject(e.getSubject());
                mimeHelper.setFrom(new InternetAddress(e.getFrom(), e.getPersonal()));

                emailSender.send(message);

                return Mono.empty();
            }
    ).subscribeOn(emailScheduler).then();
}

EDITED SERVICE I think this version of service layer is cleaner but any comments is appreciated

    public Mono<Prospect> save(final Prospect prospect) {

    return Mono.fromCallable(
            () -> transactionTemplate.execute(status -> prospectRepository.save(prospect)))
            .subscribeOn(jdbcScheduler)
            .flatMap(savedProspect -> {
                        templateService.generateProspectSubscription(savedProspect)
                                .map(t ->
                                        EmailPostRequest.builder()
                                                ...
                                                .build())
                                .flatMap(emailService::send)
                                .subscribe();

                        return Mono.just(savedProspect);
                    }
            );
}

Upvotes: 0

Views: 3546

Answers (1)

Brian Clozel
Brian Clozel

Reputation: 59086

This approach is not reactive friendly, as you're 100% wrapping blocking libraries. With this use case, you can't really see the benefit of a reactive runtime and chances are the performance of your application is worse than a blocking one.

If your main motivation is performance, than this is probably counter-productive. Offloading a lot of blocking I/O work on to specialized Schedulers has a runtime cost in term of memory (creating more threads) and CPU (context switching). If performance and scalability are your primary concern, then switching to Spring MVC and leveraging the Flux/Mono support where it fits, or even calling block() operators is probably a better fit.

If your main motivation is using a specific library, like Spring Framework's WebClient with Spring MVC, then you're better off using .block() operators in selected places rather than wrapping and scheduling everything.

Upvotes: 1

Related Questions