How to simulate a delay in a Spring reactive stack thread - spring

I am building two Spring versions of a single simple application. One of them is a Servlet stack and the other is a Reactive one. My goal is to show that the Reactive stack doesn't block threads from processing other requests when it's waiting for something else.
So, I simulate a delay in the code in both versions. However, the reactive stack seems to not handle other requests when the delay is taking effect. In other words, it's not being reactive, am I doing something wrong? Have I misunderstood the way Spring reactive works? I am simulating the delay incorrectly?
The reactive stack handler class
#Component #RequiredArgsConstructor
public class GradeHandler {
private final GradeRepository gradeRepository;
public Mono<ServerResponse> gradeHandler(ServerRequest serverRequest){
String id = serverRequest.pathVariable("id");
return ServerResponse.
ok().contentType(MediaType.APPLICATION_JSON).
body(getGradeByIdDelayed(id, Duration.ofSeconds(1)), Grade.class);
}
public Mono<ServerResponse> gradeHandler_blocking(ServerRequest serverRequest){
String id = serverRequest.pathVariable("id");
return ServerResponse.
ok().contentType(MediaType.APPLICATION_JSON).
body(getGradeByIdDelayed(id, Duration.ofSeconds(10000)), Grade.class);
}
private Mono<Grade> getGradeByIdDelayed(String id, Duration duration) {
return gradeRepository.
findById(id).
delayElement(duration);
}
}
The reactive stack router config file
#Configuration
public class GradeRouterConfig {
#Bean
public RouterFunction<ServerResponse> gradeRouter(GradeHandler gradeHandler){
return RouterFunctions.
route(GET("grade/{id}").
and(accept(MediaType.APPLICATION_JSON)), gradeHandler::gradeHandler).
andRoute(GET("blocking/grade/{id}").
and(accept(MediaType.APPLICATION_JSON)), gradeHandler::gradeHandler_blocking);
}
}
The repository
#Repository
public interface GradeRepository extends ReactiveCrudRepository<Grade, String> {
}
The entity
#Document #Data #RequiredArgsConstructor #Builder
public class Grade {
#Id
private final String id;
private final double grade;
private final String studentName;
}
This is the client JavaScript code that calls the endpoints
gradesToBeQuried = [1, 2, 3, 4, 5]
var t0 = performance.now()
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onload = () => callback(xmlHttp.responseText);
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function getRandomGradeId(grades) {
return grades[Math.floor(Math.random() * grades.length)];
}
function getURLWithRandomGradeID(grades){
randomGradeId = getRandomGradeId(grades);
return "http://localhost:8080/grade/" + randomGradeId;
}
function getURLWithRandomGradeID_blocking(grades){
randomGradeId = getRandomGradeId(grades);
return "http://localhost:8080/blocking/grade/" + randomGradeId;
}
function checkFinishConditionAndLog(currentIndex, totalNumberOfQueries) {
if (currentIndex === totalNumberOfQueries - 1) {
var t1 = performance.now()
console.log("it took " + (t1 - t0) / 1000 + " seconds.")
}
}
function queryGrade(currentIndex, totalNumberOfQueries){
let urlWithRandomGradeID = getURLWithRandomGradeID(gradesToBeQuried);
httpGetAsync(urlWithRandomGradeID, (result) => {
console.log(currentIndex + result);
checkFinishConditionAndLog(currentIndex, totalNumberOfQueries);
})
}
function queryGrade_blocking(){
let urlWithRandomGradeID = getURLWithRandomGradeID_blocking(gradesToBeQuried);
httpGetAsync(urlWithRandomGradeID, (result) => console.log("blocking thread is done"))
}
function runQueries(){
const totalNumberOfQueries = 1000;
const totalNumberOfBlockingQueries = 15;
Array(totalNumberOfBlockingQueries).fill().map((_, i) => queryGrade_blocking());
Array(totalNumberOfQueries).fill().map((_, i) => queryGrade(i, totalNumberOfQueries));
}
runQueries();

After much research I came across this answer, it turns out that the delayElement function blocks the thread.

Related

ReactiveCosmosRepository is not being invoked with webFlux and netty

I have the following reactive repository:
#Repository
public interface FooCosmosRepository extends ReactiveCosmosRepository<Foo, String> {
}
I am using it as following:
#Override
public Mono<FooResponse> getFooDetails() {
FooResponse fooResponse = new FooResponse();
fooResponse.setCount(1000);
List<Foo> fooList = new ArrayList<>();
repository.findAll().collectList().flatMap(e ->{
//This is not invoked. findAll return Flux<T> in this case Flux<Foo>
for (Foo foo : e) {
fooList.add(foo);
}
return null;
});
fooResponse.setFooList(fooList);
return Mono.just(fooResponse);
}
FooResponse is defined as follows:
#NoArgsConstructor
#Data
#FieldDefaults(level = AccessLevel.PRIVATE)
public class FooResponse {
int rowCount;
List<Foo> fooList;
}
I cant block cause I get error
Iterating over a toIterable() / toStream() is blocking, which is not supported in thread reactor-http-nio-6
I cant return Flux<T> from the method also. I need to return Mono<FooResponse>. How can i query repository, actually get/collect response and add to the list?
Any ideas?
It's because you are coding imperatively instead of reactively. and you are breaking the chain which means that Reactor can't complete the assembly phase, and then execute during the subscription phase.
#Override
public Mono<FooResponse> getFooDetails() {
return repository.findAll()
.collectList()
.map(list -> {
FooResponse fooResponse = new FooResponse();
fooResponse.setCount(1000);
fooResponse.setList(list);
return fooResponse;
});
}
This is basic reactor and I recommend the following links:
Reactor Core Features
Flight of the flux

UpdateById method in Spring Reactive Mongo Router Handler

In Spring Reactive Java how can I write an updateById() method using the Router and Handler?
For example, the Router has this code:
RouterFunctions.route(RequestPredicates.PUT("/employees/{id}").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)),
employeeHandler::updateEmployeeById);
My question is how to write the employeeHandler::updateEmployeeById() keeping the ID as same but changing the other members of the Employee object?
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
<And now what??>
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class);
}
The Employee class looks like this:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
int id;
double salary;
}
Thanks for any help.
First of all, you have to add ReactiveMongoRepository in your classpath. You can also read about it here.
#Repository
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, Integer> {
Mono<Employee> findById(Integer id);
}
Then your updateEmployeeById method can have the following structure:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(Employee.class)
.doOnSubscribe(e -> log.info("update employee request received"))
.flatMap(employee -> {
Integer id = Integer.parseInt(serverRequest.pathVariable("id"));
return employeeRepository
.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("employee with " + id + " has not been found")))
// what you need to do is to update already found entity with
// new values. Usually map() function is used for that purpose
// because map is about 'transformation' what is setting new
// values in our case
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
});
})
.flatMap(employeeRepository::save)
.doOnError(error -> log.error("error while updating employee", error))
.doOnSuccess(e -> log.info("employee [{}] has been updated", e.getId()))
.flatMap(employee -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(employee), Employee.class));
}
UPDATE:
Based on Prana's answer, I have updated the code above merging our solutions in one. Logging with a help of Slf4j was added. And switchIfEmpty() functions for the case when the entity was not found.
I would also suggest your reading about global exception handling which will make your API even better. A part of it I can provide here:
/**
* Returns routing function.
*
* #param errorAttributes errorAttributes
* #return routing function
*/
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private HttpStatus getStatus(Throwable error) {
HttpStatus status;
if (error instanceof NotFoundException) {
status = NOT_FOUND;
} else if (error instanceof ValidationException) {
status = BAD_REQUEST;
} else {
status = INTERNAL_SERVER_ERROR;
}
return status;
}
/**
* Custom global error handler.
*
* #param request request
* #return response
*/
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
Throwable error = getError(request);
HttpStatus errorStatus = getStatus(error);
return ServerResponse
.status(errorStatus)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
A slightly different version of the above worked without any exceptions:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
Integer employeeId = Integer.parseInt(serverRequest.pathVariable("id"));
employeeMono = employeeMono.flatMap(employee -> employeeRepository.findById(employeeId)
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
})
.flatMap(employeeRepository::save));
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class).switchIfEmpty(notFound);
}
Thanks to Stepan Tsybulski.

REST endpoint works when directly accessed, but not when accessed via WebClient

Note: The following is an edit/revision to a similar post/question, in an attempt to better identify my issue/question, and to provide better code samples demonstrating my issue.
Add Note: Code example has been revised to include working code.
I have two endpoints in two routers in the same spring reactive application. The first (/v2/DemoPOJO) appears to work correctly. The second (/v2/DemoClient/DemoPOJO), which uses WebClient to delegate to /v2/DemoPOJO appears to "do nothing" (although the logged output shows that DemoClientHandler.add() and DemoClient.add() are being invoked).
When I do a POST request to the /v2/DemoPOJO endpoint, the doFirst(), doOnSuccess(), and doFinally() are invoked and output the appropriate text (in "real life", a row is added to the repository).
When I do a POST request to the /v2/DemoClient/DemoPOJO endpoint, it returns a 200 OK status, but none of the expected text is output (in "real life", nothing gets added to the repository).
The following files support the /v2/DemoPOJO endpoint...
Router class implementation for DemoPOJO...
#Configuration
public class DemoPOJORouter {
private Logger logger = LoggerFactory.getLogger(DemoPOJORouter.class);
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
logger.debug("DemoPOJORouter.demoPOJORoute( DemoPOJOHandler )");
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler class implementation for DemoPOJO...
#Component
public class DemoPOJOHandler {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoPOJOHandler.add( ServerRequest )");
return request.bodyToMono(DemoPOJO.class).doFirst(() -> System.out.println("-> doFirst()."))
.doOnSuccess(demoPOJO -> System.out.println("Received >> " + demoPOJO.toString()))
.then(ServerResponse.accepted().build())
.doOnError(e -> System.out.println("-> doOnError()"))
.doFinally(demoPOJO -> System.out.println("-> doFinally()"));
}
}
DemoPOJO implementationj...
public class DemoPOJO {
private Logger logger = LoggerFactory.getLogger(DemoPOJO.class);
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
logger.debug("DemoPOJO.DemoPOJO( {}, {}, {} )", id, name, value);
this.id = id;
this.name = name;
this.value = value;
}
/*
* setters and getters go here
*/
public String toString() {
logger.debug("DemoPOJO.toString()");
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append(" :: ");
builder.append(name);
builder.append(" :: ");
builder.append(value);
return builder.toString();
}
}
The following files support the /v2/DemoClient/DemoPOJO endpoint...
Router implementation for DemoClient...
#Configuration
public class DemoClientRouter {
private Logger logger = LoggerFactory.getLogger(DemoClientRouter.class);
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
logger.debug("DemoClientRouter.route( DemoClientHandler )");
return nest(path("/v2/DemoClient"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler implementation for DemoClient...
#Component
public class DemoClientHandler {
private Logger logger = LoggerFactory.getLogger(mil.navy.demo.demopojo.DemoPOJOHandler.class);
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoClientOHandler.add( ServerRequest )");
// THIS CODE
return request.bodyToMono(DemoPOJO.class).flatMap(demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.accepted().build());
// REPLACES THIS CODE
/*
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.ok().build())
.switchIfEmpty(ServerResponse.badRequest().build());
*/
}
}
WebClient implementation for DemoClient...
#Component
public class DemoClient {
private Logger logger = LoggerFactory.getLogger(DemoClient.class);
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
logger.debug("DemoClient.add( DemoPOJO )");
logger.debug("DemoClient.add() >> DemoPOJO -> {}", demoPOJO.toString());
return client.post().uri("http://localhost:8080/v2/DemoPOJO")
.accept(MediaType.APPLICATION_JSON)
.syncBody(demoPOJO)
.exchange()
.flatMap(response -> response.bodyToMono(Boolean.class));
}
}
here is where i guess your problem is.
return request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
doOnSuccess takes a consumer, a consumer returns void not Mono<Void>.
Here is the verbose usage of a Consumer.
Mono.just("hello")
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String s) {
// See here, it returns void
}
});
Lets look at some examples:
Mono<String> helloWorld = Mono.just("Hello")
.doOnSuccess(string -> {
// This will never be executed because
// it is just declared and never subscribed to
Mono.just(string + " world");
});
helloWorld.doOnSuccess(string -> {
// This will print out Hello
System.out.println(string);
});
Mono<String> hello = Mono.just("Hello")
.doOnSuccess(string -> {
// This will print out Hello World
System.out.println(string + " World");
});
// hello hasn't been changed
hello.map(string -> {
// This will also print out Hello World
System.out.println(string + " World");
});
// This prints hello world to after we mapped it
Mono<String> helloworld = Mono.just("hello")
.map(s -> s + " World")
.doOnSuccess(System.out::println);
// Now this is what you are essentially doing
// See how this is wrong?
Mono<DemoPOJO> demoPOJO = request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> Mono.empty() );
you are calling demoClient#add that returns a Mono<Void> here you are breaking the chain because nothing is chained onto the Mono that it is returning so it never gets subscribed to because it is not in the event chain.
return request.bodyToMono(DemoPOJO.class)
.map( demoPOJO -> {
return demoClient.add(demoPOJO);
});
If you change it to map it will probably work. What is happening then is that it is taking a Mono<DemoPojo> and "mapping" it to a Mono<Void> that is returned by your add function. And suddenly it is in the event chain (callback).

WebClient is not successfully invoking "POST" operation

I am playing with Spring's WebClient. The primary implementation of the REST endpoints (in DemoPOJORouter and DemoPOJOHandler) seem to work. Also, the http.Get endpoint in DemoClientRouter and DemoClientHandler seems to work.
But, the http.Post for the DemoClient implementation "does nothing". It returns success (200), but nothing gets added to the dummy repo. I have a feeling that I need to do something in DemoClient to cause the http.Post endpoint in DemoPOJOHandler to actually execute (i.e., I believe neither the statements in DemoPOJOService.add() nor DemoPOJORepo.add() are being executed).
Based on prior pratfalls in WebFlux/reactive/functional efforts, I have a feeling that I'm not successfully subscribing, and so the statements never are invoked. But, I'm having difficulty identifying the "why".
Test code follows...
DemoClient router...
#Configuration
public class DemoClientRouter {
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoClient/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoClient"), requestHandler::add)));
}
}
DemoClient handler...
#Component
public class DemoClientHandler {
public static final String PATH_VAR_ID = "id";
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO;
int id;
// short-circuit if bad request or invalid value for id
id = getIdFromServerRequest(request);
if (id < 1) {
return ServerResponse.badRequest().build();
}
// non-blocking mechanism for either returning the Mono<DemoPOJO>
// or an empty response if Mono<Void> was returned by repo.getById()
return demoClient.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(demoPOJO), DemoPOJO.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> add(ServerRequest request) {
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.ok().build())
.onErrorResume(e -> simpleErrorReporter(e))
.switchIfEmpty(ServerResponse.badRequest().build());
}
private int getIdFromServerRequest(ServerRequest request) {
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
// short-circuit if bad request
// should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return id;
}
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch (NumberFormatException e) {
// swallow the error, return value <0 to signal error
id = -1;
}
return id;
}
private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
return ServerResponse.badRequest()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(e.getMessage());
}
}
DemoClient impl...
#Component
public class DemoClient {
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Mono<DemoPOJO> getById(int id) {
return client.get().uri("http://localhost:8080/v2/DemoPOJO/" + id)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(DemoPOJO.class));
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return client.post().uri("http://localhost:8080/v2/DemoPOJO")
.syncBody(demoPOJO)
.exchange()
.flatMap(response -> response.bodyToMono(Boolean.class));
}
}
And, the DemoPOJO stuff, starting with DemoPOJORouter...
#Configuration
public class DemoPOJORouter {
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoPOJO/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
DemoPOJOHandler...
#Component
public class DemoPOJOHandler {
public static final String PATH_VAR_ID = "id";
#Autowired
private DemoPOJOService service;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO;
int id;
// short-circuit if bad request or invalid value for id
id = getIdFromServerRequest(request);
if (id < 1) {
return ServerResponse.badRequest().build();
}
// non-blocking mechanism for either returning the Mono<DemoPOJO>
// or an empty response if Mono<Void> was returned by repo.getById()
return service.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(demoPOJO), DemoPOJO.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> add(ServerRequest request) {
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> service.add(demoPOJO))
.then(ServerResponse.ok().build())
.onErrorResume(e -> simpleErrorReporter(e))
.switchIfEmpty(ServerResponse.badRequest().build());
}
private int getIdFromServerRequest(ServerRequest request) {
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
// short-circuit if bad request
// should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return id;
}
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch (NumberFormatException e) {
// swallow the exception, return illegal value to signal error
id = -1;
}
return id;
}
private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
return ServerResponse.badRequest()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(e.getMessage());
}
}
DemoPOJOService...
#Component
public class DemoPOJOService {
#Autowired
private DemoPOJORepo demoPOJORepo;
public Mono<DemoPOJO> getById(int id) {
DemoPOJO demoPOJO = demoPOJORepo.getById(id);
return (demoPOJO == null) ? Mono.empty()
: Mono.just(demoPOJO);
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return Mono.just(demoPOJORepo.add(demoPOJO));
}
}
DemoPOJORepo...
#Component
public class DemoPOJORepo {
private static final int NUM_OBJS = 5;
private static DemoPOJORepo demoRepo = null;
private Map<Integer, DemoPOJO> demoPOJOMap;
private DemoPOJORepo() {
initMap();
}
public static DemoPOJORepo getInstance() {
if (demoRepo == null) {
demoRepo = new DemoPOJORepo();
}
return demoRepo;
}
public DemoPOJO getById(int id) {
return demoPOJOMap.get(id);
}
public boolean add(DemoPOJO demoPOJO) throws InvalidParameterException {
// short-circuit on null pointer or duplicate id
if (demoPOJO == null) {
throw new InvalidParameterException("Add failed, null object detected...");
} else if (demoPOJOMap.containsKey(demoPOJO.getId())) {
throw new InvalidParameterException("Add failed, duplicate id detected...");
}
demoPOJOMap.put(demoPOJO.getId(), demoPOJO);
// if the return statement is reached, then the new demoPOJO was added
return true;
}
}
Finally, DemoPOJO...
public class DemoPOJO {
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(int id) {
this(id, DEF_NAME, DEF_VALUE);
}
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
this.id = id;
this.name = name;
this.value = value;
}
/*
* setters and getters go here
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append(" :: ");
builder.append(name);
builder.append(" :: ");
builder.append(value);
return builder.toString();
}
}
Here is probably your problem.
DemoPOJOHandler.class
request.bodyToMono(DemoPOJO.class).doOnSuccess(demoPOJO -> service.add(demoPOJO))
DemoPOJOService.class
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return Mono.just(demoPOJORepo.add(demoPOJO));
}
doOnSuccess returns Void, but you are calling a method that wraps the "action" in a returning Mono. So the demoPOJORepo#add function will never be triggered because you have broken the event chain here. The easiest fix is to just remove the wrapping Mono and return void.
public void add(DemoPOJO demoPOJO) {
demoPOJORepo.add(demoPOJO);
}
This took me way to long to find so here are some pointers when asking a question.
The names of your classes are too like each other, it was hard to follow the codeflow.
DemoPOJOService service your names are so alike so when i saw service was it the DemoPOJOService or the DemoClientService? clear names please.
There is nothing called http.POST when you wrote that i had no idea what you where talking about.
you had problems with the POST part but you posted everything, even the working GET parts, please only post code you suspect is relevant and are part of the problem.
Explain the question more clearly, what you have done, how you do it, what your application structure is and so fourth
Your endpoint urls say nothing "/DemoClient"?
How this question could have been asked to be more clear:
I have two endpoints in two routers in the same spring reactive
application.
When I do a POST request to the "/add" endpoint, this endpoint in turn
makes an a POST call using a WebClient to the same application just on
another endpoint called "/addToMap".
When this first call returns, it returns me a 200 OK status but when i
check the map (that the second endpoint is supposed to add the posted
data to) nothing gets added.
So please, next time asking a question, be clear, very clear, a lot clearer than you think. make sure your code is clear too with good variable and class names and clear url names. If you have messy names on your own computer its fine but when posting here be polite and clean up the code .It takes 5 minutes to add good names to classes and parameters so that we understand your code quicker.
take the time to read the "how to ask a good question" please.
How to ask a good question

How to test Singleton class that has a static dependency

I have a Singleton class that uses the thread-safe Singleton pattern from Jon Skeet as seen in the TekPub video. The class represents a cached list of reference data for dropdowns in an MVC 3 UI.
To get the list data the class calls a static method on a static class in my DAL.
Now I'm moving into testing an I want to implement an interface on my DAL class but obviously cannot because it is static and has only one static method so there's no interface to create. So I want to remove the static implementation so I can do the interface.
By doing so I can't call the method statically from the reference class and because the reference class is a singleton with a private ctor I can't inject the interface. How do I get around this? How do I get my interface into the reference class so that I can have DI and I can successfully test it with a mock?
Here is my DAL class in current form
public static class ListItemRepository {
public static List<ReferenceDTO> All() {
List<ReferenceDTO> fullList;
... /// populate list
return fullList;
}
}
This is what I want it to look like
public interface IListItemRepository {
List<ReferenceDTO> All();
}
public class ListItemRepository : IListItemRepository {
public List<ReferenceDTO> All() {
List<ReferenceDTO> fullList;
... /// populate list
return fullList;
}
}
And here is my singleton reference class, the call to the static method is in the CheckRefresh call
public sealed class ListItemReference {
private static readonly Lazy<ListItemReference> instance =
new Lazy<ListItemReference>(() => new ListItemReference(), true);
private const int RefreshInterval = 60;
private List<ReferenceDTO> cache;
private DateTime nextRefreshDate = DateTime.MinValue;
public static ListItemReference Instance {
get { return instance.Value; }
}
public List<SelectListDTO> SelectList {
get {
var lst = GetSelectList();
lst = ReferenceHelper.AddDefaultItemToList(lst);
return lst;
}
}
private ListItemReference() { }
public ReferenceDTO GetByID(int id) {
CheckRefresh();
return cache.Find(item => item.ID == id);
}
public void InvalidateCache() {
nextRefreshDate = DateTime.MinValue;
}
private List<SelectListDTO> GetSelectList() {
CheckRefresh();
var lst = new List<SelectListDTO>(cache.Count + 1);
cache.ForEach(item => lst.Add(new SelectListDTO { ID = item.ID, Name = item.Name }));
return lst;
}
private void CheckRefresh() {
if (DateTime.Now <= nextRefreshDate) return;
cache = ListItemRepository.All(); // Here is the call to the static class method
nextRefreshDate = DateTime.Now.AddSeconds(RefreshInterval);
}
}
}
You can use the singleton based on instance(not based on static), for which you can declare interface like this.
public interface IListItemRepository
{
List<ReferenceDTO> All();
}
public class ListItemRepository : IListItemRepository
{
static IListItemRepository _current = new ListItemRepository();
public static IListItemRepository Current
{
get { return _current; }
}
public static void SetCurrent(IListItemRepository listItemRepository)
{
_current = listItemRepository;
}
public List<ReferenceDTO> All()
{
.....
}
}
Now, you can mock IListItemRepository to test.
public void Test()
{
//arrange
//If Moq framework is used,
var expected = new List<ReferneceDTO>{new ReferneceDTO()};
var mock = new Mock<IListItemRepository>();
mock.Setup(x=>x.All()).Returns(expected);
ListItemRepository.SetCurrent(mock.Object);
//act
var result = ListItemRepository.Current.All();
//Assert
Assert.IsSame(expected, result);
}
Which DI framework are you using? Depending on your answer, IOC container should be able to handle single-instancing so that you don't have to implement your own singleton pattern in the caching class. In your code you would treat everything as instanced classes, but in your DI framework mappings you would be able to specify that only one instance of the cache class should ever be created.
One way to test it would be if you refactor your ListItemReference by adding extra property:
public sealed class ListItemReference {
...
public Func<List<ReferenceDTO>> References = () => ListItemRepository.All();
...
private void CheckRefresh() {
if (DateTime.Now <= nextRefreshDate) return;
cache = References();
nextRefreshDate = DateTime.Now.AddSeconds(RefreshInterval);
}
}
And then in your test you could do:
ListItemReference listReferences = new ListItemReference();
listReferences.References = () => new List<ReferenceDTO>(); //here you can return any mock data
Of course it's just temporary solution and I would recommend getting rid of statics by using IoC/DI.

Resources