I try to implement a webservice using webflux and an entry point aims to create a new User. For this project I try to implement Webflux with R2DBC (Postgresql).
I have 4 classes that are created to handle user creation:
- UserDetailsRequestModel used for sending user's information
- UserResponseModel used as returned object for user
- UserDto that communicates from controller to service
- UserEntity that is used for storing data on postgres
I have a Mapper class that uses MapStruct and a repository interface that uses ReactiveCrudRepository.
The user classes are rather simple:
Payload for user creation:
public class UserDetailsRequestModel {
private String firstName;
private String lastName;
private String email;
private String password;
}
Payload returned
public class UserResponseModel {
private String userId;
private String firstName;
private String lastName;
private String email;
}
UserDto
public class UserDto implements Serializable {
@Serial
private static final long serialVersionUID = -386521462517601642L;
private Long id;
private String userId;
private String firstName;
private String lastName;
private String email;
private String password;
private String encryptedPassword;
private String emailVerificationToken;
private Boolean emailVerificationStatus = false;
}
UserEntity
I removed most of the annotation here...
public class UserEntity implements Serializable {
@Serial
private static final long serialVersionUID = -5590905788591386398L;
@Id
private Long id;
@Column
@NotNull
@Size(max = 50)
private String userId;
private String firstName;
private String lastName;
private String email;
private String encryptedPassword;
private String emailVerificationToken;
private Boolean emailVerificationStatus = false;
}
Now I have a Mapper interface that uses MapStruct:
@Mapper
public interface UserMapper {
UserMapper USERMAPPER = Mappers.getMapper( UserMapper.class );
UserDto toUserDto(UserDetailsRequestModel userDetails);
UserResponseModel toUserResponse(UserDto userDto);
UserEntity toUserEntity(UserDto userDto);
UserDto entityToUserDto(UserEntity userEntity);
}
This interface aims to help me converting request to dto, dto to response, entity to dto and dto to entity...
My repository interface is basic:
@Repository
public interface UserRepository extends ReactiveCrudRepository<UserEntity, Long> {
Mono<UserEntity> save(Mono<UserEntity> userEntity);
Mono<UserEntity> findByEmail(Mono<String> email);
}
Now I have a controller and a service layer:
I have a Mono<UserDetailsRequestModel>
object as requestbody. I want to convert this object onto Mono<UserDto>
, then call my service layer, convert this Mono<UserDto>
onto Mono<UserEntity>
, persist data, converting the Mono<UserEntity>
onto Mono<UserDto>
and finaly return a Mono<UserResponseModel>
...
@PostMapping(
produces = MediaType.TEXT_EVENT_STREAM_VALUE
)
public Mono<UserResponseModel> createUser(@RequestBody Mono<UserDetailsRequestModel> userDetailsRequestModelMono) {
return userDetailsRequestModelMono
.map(userDetailsRequestModel -> UserMapper.USERMAPPER.toUserDto(userDetailsRequestModel))
.map(userDto -> {
Mono<UserDto> userDtoMono = this.userService.createUser(Mono.just(userDto));
System.out.println("UserDto > " + userDto.toString());
return userDtoMono;
})
.flatMap(userDtoMono -> {
Mono<UserResponseModel> userResponseModelMono = userDtoMono.map(userDtoResponse -> {
UserResponseModel userResponseModel = UserMapper.USERMAPPER.toUserResponse(userDtoResponse);
System.out.println("UserResponseModel > " + userResponseModel.toString());
return userResponseModel;
});
return userResponseModelMono;
})
.doOnError(err -> System.out.println("Error caught >> " + err))
.doFinally(System.out::println);
}
In my Service I have the following implementation:
@Override
public Mono<UserDto> createUser(Mono<UserDto> userDtoMono) {
// System.out.println(userDtoMono.block().toString());
return userDtoMono
.map(userDto -> UserMapper.USERMAPPER.toUserEntity(userDto))
.flatMap(userEntity -> {
if (userRepository.findByEmail(Mono.just(userEntity.getEmail())) == null) {
// create user
userEntity.setUserId("azvxcvxcxcvcx");
userEntity.setVersion(1L);
userEntity.setEmailVerificationToken("emailVerifToken");
userEntity.setEmailVerificationStatus(Boolean.FALSE);
userEntity.setEncryptedPassword("encryptedPassword");
System.out.println("UserEntity > " + userEntity.toString());
return userRepository.save(Mono.just(userEntity));
} else {
return null;
}
})
.map(userEntity -> {
UserDto userDto = UserMapper.USERMAPPER.entityToUserDto(userEntity);
System.out.println(userDto);
return userDto;
});
}
I have 2 issues and questions:
- In my service layer, i would like to manage the case if user already exists and if so throw an exception (i'll try later to create an exception handler but at this stage that's not the point...)
- I have issue converting my objects apparently and I retrieve exception (null mono). Actually I don't get where is my error (i begin playing with webflux).
Here is my logs for the request sent:
UserDto > UserDto(id=null, userId=null, firstName=John, lastName=Wick, email=jw@mail.com, password=123, encryptedPassword=null, emailVerificationToken=null, emailVerificationStatus=null)
Error caught >> java.lang.NullPointerException: The mapper returned a null Mono
2023-03-20 21:51:55 [reactor-http-nio-3] DEBUG r.n.http.server.HttpServerOperations - [e1be5f46-1, L:/[0:0:0:0:0:0:0:1]:8090 - R:/[0:0:0:0:0:0:0:1]:63068] Decreasing pending responses, now 0
2023-03-20 21:51:55 [reactor-http-nio-3] DEBUG r.n.http.server.HttpServerOperations - [e1be5f46-1, L:/[0:0:0:0:0:0:0:1]:8090 - R:/[0:0:0:0:0:0:0:1]:63068] Last HTTP packet was sent, terminating the channel
2023-03-20 21:51:55 [reactor-http-nio-3] DEBUG r.netty.channel.ChannelOperations - [e1be5f46-1, L:/[0:0:0:0:0:0:0:1]:8090 - R:/[0:0:0:0:0:0:0:1]:63068] [HttpServer] Channel inbound receiver cancelled (operation cancelled).
2023-03-20 21:51:55 [reactor-http-nio-3] DEBUG r.n.http.server.HttpServerOperations - [e1be5f46-1, L:/[0:0:0:0:0:0:0:1]:8090 - R:/[0:0:0:0:0:0:0:1]:63068] Last HTTP response frame
onError