Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the efficient/proper way to flow multiple objects in reactor

I am new to reactive programming and to get my hand on I am trying to build a near to real example.

When you see reactor tutorials they show you very easy examples like.

return userRepository.findById(1);

or something like dealing with flux the break the "brown little fox" string and find unique letters etc. But mostly these tutorials stick to single object and unfortunately i am unable to find any guide lines or tutorial which show a side by side examples to type same code first in imperative then in reactive, thats why i see lots of new comers in reactive programming faces a lot of learning issues.

but my point is in real life applications we deals with multiple objects like below sample code I wrote in reactor. Apologies for bad code i am still learning.

public Mono<ServerResponse> response(ServerRequest serverRequest) {

        return
                Mono.just(new UserRequest())
                        .map(userRequest -> {
                            Optional<String> name = serverRequest.queryParam("name");
                            if (name.isPresent() && !name.get().isEmpty()) {
                                userRequest.setName(name.get());
                                return userRequest;
                            }
                            throw new RuntimeException("Invalid name");
                        })
                        .map(userRequest -> {
                            Optional<String> email = serverRequest.queryParam("email");
                            if (email.isPresent() && !email.get().isEmpty()) {
                                userRequest.setEmail(email.get());
                                return userRequest;
                            }
                            throw new RuntimeException("Invalid email");
                        })
                        .map(userRequest -> {
                            userRequest.setUuid(UUID.randomUUID().toString());
                            return userRequest;
                        })
                        .flatMap(userRequest ->
                                userRepository
                                        .findByEmail(userRequest.getEmail())
                                        .switchIfEmpty(Mono.error(new RuntimeException("User not found")))
                                        .map(user -> Tuples.of(userRequest, user))
                        )
                        .map(tuple -> {
                            String cookiePrefix = tuple.getT2().getCode() + tuple.getT1().getUuid();
                            return Tuples.of(tuple.getT1(), tuple.getT2(), cookiePrefix);
                        })
                        //Some more chaining here.
                        .flatMap(tuple ->
                                ServerResponse
                                        .ok()
                                        .cookie(ResponseCookie.from(tuple.getT3(), tuple.getT2().getRating()).build())
                                        .bodyValue("Welcome")
                        );

    }

consider above code first i started with UserRequest object to map querystring in this object. then i need some data from database and so on reactive chaining continue more works to do. Now consider

  • UserRequest Object from first chaining method and
  • User document i fetched from db then i do lot more operations but at the end of chaining i need both of these objects to process final response. The only way to achieve that i found on google is Tuple. but the code look like more dirty after that since in every next operator i have to do
tuple.getT()
tuple.getT2()

So finally i would like to ask is that the proper way or i am missing something here. Because i learned one thing in reactive that data flows nothing more but like in imperative in the middle of logic we got oh i need another variable/object so i define it on top and use it but in reactive after 5th or 6th operator when developer realize ohh i need that object too here that was i created in 2nd operator then i have to go back and pass that in chaining to get in my 5th or 6th operator is that a proper way to do that.

like image 356
Muhammad Ilyas Avatar asked Jul 03 '20 19:07

Muhammad Ilyas


People also ask

What is difference between mono and flux?

A Flux object represents a reactive sequence of 0.. N items, while a Mono object represents a single-value-or-empty (0..1) result. This distinction carries a bit of semantic information into the type, indicating the rough cardinality of the asynchronous processing.

What is Project reactor used for?

Project Reactor is a fully non-blocking foundation with back-pressure support included. It's the foundation of the reactive stack in the Spring ecosystem and is featured in projects such as Spring WebFlux, Spring Data, and Spring Cloud Gateway.

What is Reactor framework?

Reactor is a fourth-generation reactive library, based on the Reactive Streams. specification, for building non-blocking applications on the JVM.


1 Answers

There's generally two strategies that can be used to avoid "tuple hell", sometimes in isolation & sometimes in tandem:

  • Use your own "custom" tuple class that's much more descriptive of types (I would nearly always recommend this in production code rather than using the built-in Tuple classes);
  • Concatenate some of your map() / flatMap() calls so that declaring tuples isn't required.

In addition, there's more rules to bear in mind that can help things in general here:

  • Never mutate objects in a reactive chain unless you have no other choice - use immutable objects with the @With pattern instead;
  • Don't use multiple map() calls chained together for returning the same type - favour doing everything in a single map call instead;
  • Farm reusable elements of a long reactive chain out to separate methods, and embed them in your main reactive chain using map(), flatMap() or transform().

If we take the above examples into practice, we can farm the first three map calls out into a single method that "populates" the user object, using the @With style rather than setters (though you can use setters here if you really must):

private UserRequest populateUser(UserRequest userRequest, ServerRequest serverRequest) {
    return userRequest
            .withName(serverRequest.queryParam("name")
                    .filter(s -> !s.isEmpty())
                    .orElseThrow(() -> new RuntimeException("Invalid name")))
            .withEmail(serverRequest.queryParam("email")
                    .filter(s -> !s.isEmpty())
                    .orElseThrow(() -> new RuntimeException("Invalid email")))
            .withUuid(UUID.randomUUID().toString());
}

We can also farm out the part of the chain that looks up a user from the database. This part likely will need some form of new type, but instead of a Tuple, create a separate class - let's call it VerifiedUser - which will take the userRequest and user objects. This type can then also be responsible for generating the response cookie object, and providing it via a simple getter. (I'll leave writing the VerifiedUser task as an exercise for the author - that should be pretty trivial.)

We'd then have a method like this:

private Mono<VerifiedUser> lookupUser(UserRequest userRequest) {
    return userRepository
            .findByEmail(userRequest.getEmail())
            .map(user -> new VerifiedUser(userRequest, user)) //VerifiedUser can contain the logic to produce the ResponseCookie
            .switchIfEmpty(Mono.error(new RuntimeException("User not found")));
}

So now we have two separate, small methods, which each take on a single responsibility. We also have another simple type, VerifiedUser, which is a named container type that's much more descriptive & useful than a Tuple. This type also gives us a cookie value.

This process has meant our main reactive chain can now become very simple indeed:

return Mono.just(new UserRequest())
        .map(userRequest -> populateUser(userRequest, serverRequest))
        .flatMap(this::lookupUser)
        .flatMap(verifiedUser ->
                ServerResponse.ok()
                        .cookie(verifiedUser.getCookie())
                        .bodyValue("Welcome")
        );

The end result is a chain that's safer (since we're not mutating a value in the chain, everything is kept immutable), much clearer to read, and much easier to extend in the future should we ever need to. If we need to go further then we could as well - if the methods created here needed to be used elsewhere for instance, they could easily be farmed out as spring beans conforming to a functional interface, then injected at will (and easily unit tested.)

(As an aside, you're certainly correct that, at the time of writing, there's plenty of trivial tutorials but very little "in-depth" or "real-world" material out there. Such is often the case with reasonably new frameworks, but it certainly makes them hard to master, and results in lots of unmaintainable code out there in the wild!)

like image 92
Michael Berry Avatar answered Oct 27 '22 02:10

Michael Berry