Can't save user to mongodb in Spring 5 WebFlux - java

I try to save User to Mongo database in reactive style with Spring WebFlux, but data isn't saved, so I have Java 8, Spring 5 and mongodb.
This is my config:
My entity:
#Document
public class User {
#Id
private String id;
private String username;
private String password;
}
My repository:
public interface UserRepository extends ReactiveMongoRepository<User, String> {
}
My web config:
#Configuration
#EnableWebFlux
public class WebConfig {
#Autowired
private UserRepository userRepository;
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
Mono<User> user = request.bodyToMono(User.class);
Mono<User> userMono = user.doOnNext(userRepository::save)
return ServerResponse.().body(userMono, User.class);
});
}
}
May be I have to manual execute subscribe after save to repository, but I havn't found such info in spring reference?
UPD:
But if I extract from request User and pass it to userRepository and return result I have got a successful result to save data to mongo. But I am not sure that it is a good approach:
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
User user = request.bodyToMono(User.class).block();
Mono<User> userMono = userRepository.save(user);
return ServerResponse.ok().body(userMono, User.class);
});
}

I have changed doOnNext to flatMap and it works, so it seems to me this is due to the fact that "Transform the item emitted by this Mono asynchronously, returning the value emitted by another Mono" and doOnNext - "Add behavior triggered when the Mono emits a data successfully" and userRepository.save(...) method return Mono that doOnNext add behavious to old Mono and return it(I am loosing response from save method) and flatMap return new Mono which got from save method:
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
Mono<User> user = request.bodyToMono(User.class);
Mono<User> userMono = user.flatMap(userRepository::save);
return ServerResponse.status(HttpStatus.CREATED).body(userMono, User.class);
});
}

Related

Unable to return a request within webflux in reactive manner

Here is the following flow in my code:
User calls create agency endpoint, containing the agency name and a boolean field ->
Router functions receives call, passes to handler ->
Handler converts request body to Mono, passes that mono so a service ->
Service saves agency to DB, generating an ID in the process, then creates a Response object containing the ID wrapped in a Mono, returns Response back to the Handler.
This is the part where I am stuck. Maintaining a reactive approach, I must save the Agency to Db and create the ID within a "doOnNext() part of the Mono. However, I can't return the ID that I get from the DB in order to create the response object.
What is the reactive way to accomplish this? Thanks in advance!
public RouterFunction<ServerResponse> createAgency() {
return RouterFunctions
.route(POST("/agency").and(accept(MediaType.APPLICATION_JSON)), handler::createAgency);
}
#Component
#AllArgsConstructor
public class AgencyHandler {
private final AgencyServiceImpl service;
#NonNull
public Mono<ServerResponse> createAgency(ServerRequest request){
Mono<CreateAgencyRequest> agency = request.bodyToMono(CreateAgencyRequest.class);
Mono<CreateAgencyResponse> response = service.createAgency(agency);
return ServerResponse.created(URI.create("/agency"))
.contentType(MediaType.APPLICATION_JSON)
.body(response, CreateAgencyResponse.class);
}
}
#Service
#AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
private final AgencyRepository agencyRepository;
private final UuidGenerator uuidGenerator;
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
UUID id;
request.doOnNext(agency -> {
UUID agencyId = uuidGenerator.getUUID();
Mono<Agency> newAgency = Mono.just(
new Agency(
agencyId,
agency.getFields().getName()
));
//repository.save(newAgency)
//return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
});
// return request
return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
}
}
Something like the following should do the trick:
#Service
#AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
private final AgencyRepository agencyRepository;
private final UuidGenerator uuidGenerator;
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
UUID agencyId = uuidGenerator.getUUID();
return request.flatMap(createAgencyRequest -> {
Agency agency = new Agency(agencyId, agency.getFields().getName();
return repository.save(newAgency);
}).map(saved ->
new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString()));
)
}
}
You would create the Agency in the flatMap operation and store it in the database. I assume your repository is also reactive so it should return Mono as well, hence the flatMap operation. Afterwards you just need to map whatever the repository returned (you may want to have some logic here based on the successful operation on the database) to create the CreateAgencyResponse.
ThedoOnNext is not a good option to perform I/O bound operations such as database access. You should use flatmap instead. Also, have a look at map operator for synchronous, 1-to-1 transformations.
The final code should looks like this:
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
return request.map(req -> new Agency(uuidGenerator.getUUID(), req.getFields().getName()))
.flatMap(agency -> repository.save(agency))
.map(agency -> new CreateAgencyResponse(new CreateAgencyResponseData(agency.getAgencyId())));
}
doOnNext is for side effects, what you are looking for is flatMap
return request.flatMap(agency -> {
final UUID agencyId = uuidGenerator.getUUID();
return repository.save(new Agency(agencyId, agency.getFields().getName()))
.thenReturn(agencyId);
}).flatMap(id -> ServerResponse.ok()
.bodyValue(new CreateAgencyResponseData(agencyId.toString()))
.build());
Wrote this without a compiler to check the code but you should get the gist of it.

How can i treat UserNotFound exception on endpoints unit tests?

I'm working on a spring boot CRUD RESTful API with an User entity that consists of two parameters : name and id. My test framework is JUnit.
The problem i'm facing is that i don't know how to treat a throwable UserNotFound exception on my services unit tests.
I have possible "User not found by {id}" exceptions in my "List user by id", "Delete user by id" and "Update user by id" as you can see (i'll only list two endpoints to make this shorter) :
#Service
public class DeleteUserService {
#Autowired
UserRepository repository;
public void deleteUser(Long id) {
Optional<User> userOptional = repository.findById(id);
if (!userOptional.isPresent()) {
throw new UserNotFoundException(id);
} else {
repository.deleteById(id);
}
}
}
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
Nothing wrong so far, my endpoints are working fine.
The UserNotFound code is :
#ControllerAdvice
public class UserNotFoundAdvice {
#ResponseBody
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
String userNotFoundHandler(UserNotFoundException ex) {
return ex.getMessage();
}
}
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("Could not find user with id " + id + ".");
}
}
The unit tests (the main reason this is being written) :
#RunWith(MockitoJUnitRunner.class)
public class DeleteUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DeleteUserService deleteUserService;
#Test
public void whenGivenId_shouldDeleteUser_ifFound(){
User user = new User();
user.setId(89L);
deleteUserService.deleteUser(user.getId());
verify(userRepository).deleteById(user.getId());
}
}
#RunWith(MockitoJUnitRunner.class)
public class DetailUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DetailUserService detailUserService;
#Test
public void whenGivenId_shouldReturnUser_ifFound() {
User user = new User();
user.setId(89L);
Optional<User> userMock = Optional.of(user);
when(userRepository.findById(user.getId())).thenReturn(userMock);
Optional<User> expected = detailUserService.listUser(user.getId());
assertThat(expected).isSameAs(userMock);
verify(userRepository).findById(user.getId());
}
}
As you can see, there's something missing in these unit tests code which is the behavior of the UserNotFound. Perhaps it is not properly mocked or something else's missing in the unit tests code??
Would really appreciate if someone could help me with this one! Sorry if the post's too long, i tried my best to explain it!
If I understand you right you need to test the behavior when the user is not found and you throw an exception.
Here is the link about how to test exception: https://www.baeldung.com/junit-assert-exception
And also additionally you can verify that delete by id or find by id weren't invoked:
verify(userRepository, never()).findById(user.getId());
or
verify(userRepository, Mockito.times(0)).findById(user.getId());
and for the deleteById the same
To test that exception handlers were invoked and worked correctly you need integration tests.

Which practice is better when throwing an exception in a service?

I'm working on a Spring Boot CRUD RESTful API and i'm trying to define the best way of doing certain things, for instance :
This is my List user by its id endpoint service :
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
And this is another way of writing it :
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public User listUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}
Both ways work but how do i know which is better?
Using java-8 is always a better choice for less code and more readable code.
You can use below tyle of as you mentioned it as your second option.
Using the Optional.orElseThrow() method represents another elegant alternative to the isPresent()-get() pair
You can find out more here
https://dzone.com/articles/using-optional-correctly-is-not-optional
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public User listUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}

Type Mismatch cannot convert from type Optional<User> to User

I am trying to create a website that allows the user to update, edit, delete, etc., and I have got to the part of Updating or Editing user’s information. I have tried multiple times using different ways, but I cannot seem to get past the error. I am completely oblivious to Optional<> I just don’t get it. I have read https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html, but i dont understand how it should be coded, its just not clicking. If someone could please inform on how it should be coded in my code and please explain it I would be so grateful. Maybe its because im overworked and have not slept, but i cannot seem to correct this error. This is the error i get on the page when i attempt to edit the information for a user:
There was an unexpected error (type=Internal Server Error, status=500).
For input string: "id"
java.lang.NumberFormatException: For input string: id
//Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
here is the UserService
//UserService
#Service
#Transactional
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository=userRepository;
}
public void saveMyUser(User user) {
userRepository.save(user);
}
public List<User> showAllUsers(){
List<User> users = new ArrayList<User>();
for(User user: userRepository.findAll()) {
users.add(user);
}
return users;
}
public void deleteMyUser(int id) {
userRepository.deleteById(id);
}
public User editUser (int id) {
return userRepository.findById(id);//I also get an error here as well
}
}
here is the controller
//Application Controller
#Controller
public class ApplicationController {
#Autowired
private UserService userService;
// THIS IS WHERE I GET THE ERROR
#RequestMapping("/edit-user")
public String editUser(#RequestParam int id,HttpServletRequest request) {
/* OPTIONAL<?> is this where i would implement the
optional what do i have to put here exactly?
I tried some ways I read about but its not working for me */
request.setAttribute("user", userService.editUser(id));
request.setAttribute("mode", "MODE_UPDATE");
return "welcome";
}
}
Thank you for the help in advance Im a little frustrated with this because I have been trying to correct this error all night.
There are several ways to convert from an option to an entity. You can use the following:
Use get() method:
public User editUser (int id) {
return userRepository.findById(id).get();
}
Use orElse method:
public User editUser (int id) {
/* new User() is stab if user was not found */
return userRepository.findById(id).orElse(new User());
}
Use orElseThrowMethod:
public User editUser (int id) {
/* Throw exception if user was not found*/
return userRepository.findById(id).orElseThrow(IllegalArgumentException::new));
}
As for controller it will be like this:
#RequestMapping("/edit-user")
public String editUser(#RequestParam int id,HttpServletRequest request) {
User user = userService.editUser(id);
request.setAttribute("user", user);
request.setAttribute("mode", "MODE_UPDATE");
return "welcome";
}
Also there similar question for your topic:
Spring Boot. how to Pass Optional<> to an Entity Class

Different return types for function Java

I'm using Spring Boot with Data JPA.
I have the following code.
A User Class with name and an informative message.
class UserResponse{
private String name;
private String message;
}
User JPA Repository which finds userBy id;
interface UserRepository{
Optional<User> findUserById(String id);
}
User Service which invokes repo and set message if user not found
class UserService(){
UserResponse user = new UserResponse();
public UserResponse getUserById(String userId){
Optional<User> useroptional = userRepository.findById(userId);
if(userOptional.isPresent()){
user.setName(userOptional.get().getName());
}else{
user.setMessage("User Not Found");
}
}
UserController has to set proper HTTP status code as per the message.
class UserController(){
public ResponseEntity<UserResponse> getUserById(String id){
UserResponse user = userService.getUserById(id);
HttpStatus status = OK;
if(!StringUtils.isEmpty(user.getMessage())){
status = NOT_FOUND;
}
return new ResponseEntity<>(user,status);
}
}
The problems I have faced is inorder to set proper status code in controller layer I have to inspect user message,which i didn't like.
Is there anyway we can create a control flow for Success and Failure cases.
Say One Return type and flow for Success scenario and vice-versa.
I know Scala has this feature with Either keyword.
Is there any alternate in Java ?
Or any other approach I can use to handle this better...
One approach would be returning RepsonseEntity in service layer itself with proper status code but setting status code is controller's Responsibility is what I felt.
In case of failure you can throw custom Exception with proper message. Then you can catch it in #ControllerAdvice. I'll add an example in a moment.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> exception(MyCustomException e) {
return new ResponseEntity(e.getMessage(), HttpStatus.NotFound);
}
}
In one #ControllerAdvice one could have more methods listening for different Exceptions. Custom Exception can hold whatever you want - it's a normal class - so you can return ResponseEntity of whatever you want.
For example:
#Transactional(readOnly = true)
#GetMapping("/{id}")
public ResponseEntity<?> getUserById(#PathVariable("id") String userId) {
return userRepository.findById(userId)
.map(user -> ResponseEntity.ok().body(user))
.orElse(new ResponseEntity<>(/* new ErrorMessage */, HttpStatus.NOT_FOUND))
}
For 'not found' response you have to create an error message object and return it to client.

Categories