I am a beginner to the spring webflux. We are currently migrating our application to Spring Webflux. No I have stuck with a problem. The following is my scenario.
The main service class is calling the following service classes for data
StudentService - return Mono<Student>
StaffService - return Mono<Staff>
Here I have a wrapper class StudentWithMentor to store the result from these service classes.
public class StudentWithMentor {
private Student student;
private Staff mentor;
}
Now in controller I am calling the above 2 services and map it into 'StudentWithMentor' in the following way
Mono<StudentWithMentor> studentWithMentorMono = Mono.just(new StudentWithMentor());
return studentWithMentorMono.map(s->{
studentService.getStudentById(id)
.doOnSuccess(s::setStudent)
.doOnSuccess(st->staffService.getStaffByGrade(st.getGrade()));
return s;
});
But when I call this endpoint I am getting the following result in postman
{
"student": null,
"mentor": null
}
Note: I am getting result from the underlying services when I debugg. But the call is returning before it process.
How can I achieve this in a complete non-blocking way.
Appreciates any help.
The easiest way will be to to use a zipWith operator to merge the results into StudentWithMentor object.
See the code below:
Mono<StudentWithMentor> studentWithMentorMono = studentService.getStudentById(id)
.zipWhen(student -> staffService.getStaffByGrade(student.getGrade()), StudentWithMentor::new);
Related
I'm trying to develop a simple crud application using Spring and Mongodb.
When I'm trying to develop view single data function, I get no error.
But it return value as null when I try in Postman.
Could you please help me to find what is the wrong with my code?
Controller
#GetMapping("/patient/{id}")
public Optional<Patients> findTicketById(#PathVariable("id") #NotNull String id){
System.out.println(id);
return patientRepository.findById(id);
}
Repository
#Repository
public interface PatientRepository extends MongoRepository<Patients, Long> {
Optional<Patients> findById(String id);
}
You can use ifPresentOrElse , check for the usages :
Functional style of Java 8's Optional.ifPresent and if-not-Present?
I was browsing through a lot of articles and blogs to find the proper way to get the following running. I already achived the following:
Spring Boot Application - works
Imperative Spring Data MongoDB Connection to my Mongo Atlas Cluster - works
Spring GraphQL Starter implementation (Resolvers, etc.) - works
Now I want to implement my last requirement. To have GraphQL subscriptions working I need to integrate the Spring Data MongoDB Reactive dependency and create a new GraphQL Resolver for Subscriptions. Here is the code that I added to the already working app (hopefully the code fragments give enough info to help me out).
Gradle.kt
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
MyApp.kt
#SpringBootApplication
#EnableReactiveMongoRepositories(basePackages = ["com.myapp"])
#EnableMongoRepositories(basePackages = ["com.myapp"])
class MyApp
fun main(args: Array<String>) {
runApplication<MyApp>(*args)
}
SubscriptionResolver.kt
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Flux<Character> {
return characterReactiveRepository.findAll()
}
}
CharacterReactiveRepository.kt
interface CharacterReactiveRepository : ReactiveMongoRepository<Character, String>
character.graphqls
type Subscription {
allCharacters: [Character]!
}
Error
SchemaClassScannerError: Unable to match type definition (NonNullType{type=ListType{type=TypeName{name='Character'}}}) with java type (reactor.core.publisher.Flux<com.backend.domain.Character>): Java class is not a List or generic type information was lost: reactor.core.publisher.Flux<com.backend.domain.Character>
Detailed Exception
https://pastebin.com/sEWmDaTE
Edit 1
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Publisher<Character> {
return characterReactiveRepository.findAll()
}
}
According to the sample, you should return:
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Publisher<List<Character>> {
return characterReactiveRepository.findAll()
}
}
Example application you can find here https://github.com/graphql-java-kickstart/samples/tree/master/spring-boot-webflux
My problem was that I defined the subscription's method's return value as [Character]! meaning an array. But since Flux<Character> or Publisher<Character> is not an array but a single type in that context the resolving failed all the time.
Changing the schema to the following helped:
type Subscription {
allCharacters: Character
}
While working on RestAssured I came across the concept of Serialization and DeSerialization(POJO Classes) to read and validate the response. I went through some tutorial and was able to create the POJO class based on my response.
However, when I use the POJO class reference in my Tests I am not able to use the then() block for different assertions. Below details might clear things bit more :
TestMethod without POJO :
public void listUsers() {
RestAssured.baseURI="https://reqres.in/";
Response res = RestAssured.given()
.contentType("application/json")
.queryParam("page", 2)
.when()
.get("/api/users")
.then()
.assertThat().statusCode(200).and()
.body("page", Matchers.equalTo(2)).and()
.body("total", Matchers.greaterThanOrEqualTo(1))
.body("data.email", Matchers.hasItem("george.edwards#reqres.in"))
.extract().response();
JsonPath jsonpath = new JsonPath(res.asString());
System.out.println(jsonpath.get("data[0].email"));
}
Test Method with POJO :
public void listUserswithPOJO() {
RestAssured.baseURI="https://reqres.in/";
ListUsers res = RestAssured.given()
.contentType("application/json")
.queryParam("page", 2)
.when()
.get("/api/users").as(ListUsers.class);
System.out.println(res.getData().get(1).getEmail());
}
Test Class :
#Test
public void listUsersTest() {
ReqResApi TS1 = new ReqResApi();
TS1.listUserswithPOJO();
}
I want to keep the assertions of the then block as it is while using POJO classes as well. When I try to do so after as(ListUser.class), it gives the compilation error that then() is undefined for ListUser class.
Is there any way in which I can use both POJO class as well as then() block in my rest assured tests.
This is not possible because Return types of these options are different.
MainPojo m1 =RestAssured.given().contentType("application/json").queryParam("page", 2).when().get("/api/users")
.as(MainPojo.class)==> Return Type is ur Class, in this example Main Pojo
System.out.println(m1.getData().get(0).getFirst_name());
RestAssured.given().contentType("application/json").queryParam("page", 2).when()
.get("/api/users").then().assertThat().statusCode(200).and().body("page", Matchers.equalTo(2)).and()
.body("total", Matchers.greaterThanOrEqualTo(1))
.body("data.email", Matchers.hasItem("george.edwards#reqres.in")).extract().response();---> Return Type is Response
i have post method in rest controller and i want to create a test for this method:
This is my method:
#PostMapping("/persons")
public ResponseEntity<PersonDto> createPerson(#RequestBody PersonDto personDto) {
try {
personService.createPerson(personDto);
return ResponseEntity.ok(personDto);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
I have no idea how this test with mock should look like.
As you use Spring, I recommend you to use #WebMvcTest to mock all things but the Controller under test.
You should so explicitly mock the dependencies of this Controller. In your case, mocking the personService field is enough.
In your test class declare a personService field annotated with #MockBean to create a Mockito mock of the class that will be added to the Spring context.
Then record a behavior for this mock.
You have two branches here : it works and an exception is risen.
So you could define two test method and record a specific behavior in each one.
At last assert the gotten response from the controller.
I already have test for deleting method. Yes i use springboot, jpa, rest, h2:
#Test
public void shouldDeletePersonById() throws Exception {
Mockito.doCallRealMethod().when(personService).deleteById(1L);
mockMvc.perform(delete("/persons/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
But for create person this looks for me very hard. I don't know what i should call of in posy method(like in delete i call id to find what i want to delete).
I am trying to use Restful API plugin (Restful API plugin). (using Grails 2.3.8, Groovy 2.1)
As stated in documentation I created a Grails service that implements RestfulServiceAdapter.
import net.hedtech.restfulapi.RestfulServiceAdapter
import com.game.trivia.Question
#Transactional
class QuestionService implements RestfulServiceAdapter {
#Override
public Object list(def service, Map params) throws Throwable{
List Q = Question.list(params)
return Q;
}
.
.
.
When trying to access the service: http://localhost:8080/test_triv/api/questions
I received following exception:
{"errors":[{"type":"general",
"errorMessage":"No signature of method: test_triv.QuestionService.list() is applicable for argument types:
(org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap) values:
[[pluralizedResourceName:questions, action:[...], ...]]\nPossible solutions: list(java.lang.Object, java.util.Map),
is(java.lang.Object), wait(), find(), wait(long), with(groovy.lang.Closure)"}]}
So I implemented another list method (which is not part of the interface):
public Object list(Map params) throws Throwable {
List Q = Question.list(params)
return Q;
}
Which works ok.
Am I doing something wrong?
Do I implement the correct interface?
Do I have to expose a service for each domain or there is any way to use an existing controller instead of a service?
Creating new service is a big overhead! I already have controllers for all domains.
Just got reply on this issue from Charlie (The pludin developer):
Our documentation should be clearer in this area, so I'll take an action to look at improving it.
You should not implement the RestfulServiceAdapter within a service, but implement and register an adapter that implements this interface if you need to adapt an existing service that does not provide the expected methods.
Since you are writing a new service, you can just expose the required methods (you don't need to implement any interface). Note the contract is essentially the same as the adapter interface, without the 'service' argument that represents the service to which the adapter would delegate.
To avoid needing an adapter, a service should expose these methods:
def list( Map params ) throws Throwable { ... }
def count( Map params ) throws Throwable { ... }
def show( Map params ) throws Throwable { ... }
def create( Map content, Map params ) throws Throwable { ... }
def update( def id, Map content, Map params ) throws Throwable { ... }
void delete( def id, Map content, Map params ) throws Throwable { ... }
The controller is intended to delegate to a service that contains business logic, and it cannot delegate to another controller. Our expectation is the RestfulApiController and other controllers within an application would share services (e.g., a ThingController and the RESTfulApiController could both use the same ThingService so that business logic is not duplicated).