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
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.
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.
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.
Reactor is a fourth-generation reactive library, based on the Reactive Streams. specification, for building non-blocking applications on the JVM.
There's generally two strategies that can be used to avoid "tuple hell", sometimes in isolation & sometimes in tandem:
Tuple
classes);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:
@With
pattern instead;map()
calls chained together for returning the same type - favour doing everything in a single map call instead;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!)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With