Check for -->
My model looks something like this.
@Document
public class PlanDetails {
@Id
private String id;
private String name;
private Double balance;
private Double internet;
private Date date;
--> //String of id's basically.
private List<String> members;
public PlanDetails(){}
public PlanDetails(String id, String name, Double balance, Double internet, Date date, List<String> members){
this.id = id;
this.name = name;
this.balance = balance;
this.internet = internet;
this.date = date;
this.members = members;
}
public String getId() {
return id;
}
/* Getter setters ommited for brevity */
Here is my handler class to handle my functional endpoints.
package com.startelco.plandetailsapi.handler;
import com.startelco.plandetailsapi.model.PlanDetails;
import com.startelco.plandetailsapi.repository.PlanRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@Component
public class PlanDetailsHandler {
private PlanRepository repository;
public PlanDetailsHandler(PlanRepository repository){
this.repository = repository;
}
//Get All Users
public Mono<ServerResponse> getAllUsers(ServerRequest request){
Flux<PlanDetails> users = repository.findAll();
return ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(users,PlanDetails.class);
}
--> //Get User by ID
public Mono<ServerResponse> getUserDetails(ServerRequest request){
String id = request.pathVariable("id");
Mono<PlanDetails> userMono = repository.findById(id);
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
return userMono.flatMap(user ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(user))
.switchIfEmpty(notFound)
);
}
//Create a user
public Mono<ServerResponse> saveUser(ServerRequest request){
Mono<PlanDetails> userMono = request.bodyToMono(PlanDetails.class);
return userMono.flatMap(user ->
ServerResponse.status(HttpStatus.CREATED)
.contentType(APPLICATION_JSON)
.body(repository.save(user),PlanDetails.class)
);
}
//Update user by ID
public Mono<ServerResponse> updateUser(ServerRequest request) {
String id = request.pathVariable("id");
Mono<PlanDetails> existingUserMono = this.repository.findById(id);
Mono<PlanDetails> userMono = request.bodyToMono(PlanDetails.class);
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
return userMono.zipWith(existingUserMono,
(user, existingUser) ->
new PlanDetails(existingUser.getId(), user.getName(), user.getBalance(), user.getInternet(),user.getDate(),user.getMembers())
)
.flatMap(user ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(repository.save(user), PlanDetails.class)
).switchIfEmpty(notFound);
}
//Delete All Users
public Mono<ServerResponse> deleteAllUsers(ServerRequest request) {
return ServerResponse.ok()
.build(repository.deleteAll());
}
}
QUESTION: What is the Reactive way of coding getMemberDetails
with reusing the previously written function getUserDetails.
?
Requirement.
DESIRED FUNCTIONALITY : Get Member Details Function.
public Mono<ServerResponse> getMemberDetails(ServerRequest request) {
--> //Pseudocode
String id = request.pathVariable("id");
Mono<PlanDetails> userMono = repository.findById(id);
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
if(userMono.member is not null){
int length = userMono.member.length
Flux<PlanDetails> memberFlux;
for(int i=0;i<length;i++){
Mono<PlanDetails> member = getUserDetails(id=userMono.member[i]);
memberFlux.add(member);
}
}else{
return ServerResponse.ok().build(memberFlux=null or empty array);
}
return ServerResponse.ok().build(memberFlux.flatmap);
}
DESIRED BEHAVIOR
{
"id": "5b42ecc11cde674475cab39a",
"name": "Alex Svirsky",
"balance": 140,
"internet": 20,
"date": 1531107095659,
"members": [
"5b42ecdd1cde674475cab39b",
"5b42ed421cde674475cab39c",
"5b42ed5d1cde674475cab39d"
]
}
REST call in my routes is defined as http://localhost:8080/users/members/5b42ecc11cde674475cab39a
[ {
"id": "5b42ecdd1cde674475cab39b",
"name": "Bob Marley",
"balance": 120,
"internet": 9,
"date": 1531107095559,
"members": null
},
{
"id": "5b42ed421cde674475cab39c",
"name": "Charlie Sheen",
"balance": 10,
"internet": 9,
"date": 1531107095555,
"members": null
},
{
"id": "5b42ed5d1cde674475cab39d",
"name": "Dale Carnegie",
"balance": 100,
"internet": 9,
"date": 1531107055555,
"members": null
}
]
Project Reactor is the implementation of Reactive Streams specification. Reactor provides two types: Mono: implements Publisher and returns 0 or 1 elements. Flux: implements Publisher and returns N elements.
Mono and Flux are both implementations of the Publisher interface. In simple terms, we can say that when we're doing something like a computation or making a request to a database or an external service, and expecting a maximum of one result, then we should use Mono.
A Flux object represents a reactive sequence of 0.. N items, while a Mono object represents a single-value-or-empty (0..1) result.
Spring WebFlux internally uses Project Reactor and its publisher implementations, Flux and Mono. The new framework supports two programming models: Annotation-based reactive components. Functional routing and handling.
In my opinion there isn't much that can be reused in that first method: as the two methods both return a fully-formed ServerResponse
, you can't compose the originals. Which is merely repository.findById()
with some boilerplate to transform that into an ok
response or a 404
response...
So what you actually need is a way to compose getMemberDetails
on top of many calls to repository.findById
. If I interpret your pseudocode correctly, you want the response to just be a Flux<PlanDetail>
of all the "members" under an original "user" (disregarding information about said original user)?
You should be able to do that reactively with a flatMap
:
public Mono<ServerResponse> getMemberDetails(ServerRequest request) {
String id = request.pathVariable("id");
Mono<PlanDetails> userMono = repository.findById(id);
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
return userMono
//transform from user mono to Flux of member details
.flatMap(user -> Flux.fromArray(user.member)) //if `member` is an Iterable use the following instead:
//.flatMapIterable(user -> user.member)
//now we have a Flux of member IDs, go get details
.flatMap(repository::findById)
//this will naturally ignore not found members
//if no member is found or array of IDs is empty, the main sequence is itself empty at this point
.switchIfEmpty(notFound);
}
Only caveat of this approach is that it doesn't distinguish between "not found" (the original user is not found) vs "no content" (the user has no members that can be found).
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