在编写反应链时,我应该依赖 "mono of item" 还是 "plain item" 参数?
Should I rely on "mono of item" or "plain item" arguments when composing reactive chains?
我有 两个版本 Webflux/Reactor 处理程序 class。此 class 模仿用户注册用例 - 就业务逻辑而言。
class 的第一个版本依赖于 Mono<User>
,而第二个版本使用普通的 User
。
class的第一个版本:这是依赖于Mono<User>
参数的版本链。注意顶级 public 方法 createUser
使用 userMono
.
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
Mono<User> userMono = serverRequest.bodyToMono(User.class).cache();
return validateUser(userMono)
.switchIfEmpty(validateEmailNotExists(userMono))
.switchIfEmpty(saveUser(userMono))
.single();
}
private Mono<ServerResponse> validateUser(Mono<User> userMono) {
return userMono
.map(this::computeErrors)
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private AbstractBindingResult computeErrors(User user) {
AbstractBindingResult errors = new BeanPropertyBindingResult(user, User.class.getName());
userValidator.validate(user, errors);
return errors;
}
private Mono<ServerResponse> validateEmailNotExists(Mono<User> userMono) {
return userMono
.flatMap(user -> userRepository.findByEmail(user.getEmail()))
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(Mono<User> userMono) {
return userMono
.flatMap(userRepository::save)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
}
class的第二个版本:即依赖于User
参数的版本。
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user ->
validateUser(user)
.switchIfEmpty(validateEmailNotExists(user))
.switchIfEmpty(saveUser(user))
.single()
);
}
private Mono<ServerResponse> validateUser(User user) {
return Mono.just(new BeanPropertyBindingResult(user, User.class.getName()))
.doOnNext(err -> userValidator.validate(user, err))
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private Mono<ServerResponse> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(User user) {
return userRepository.save(user)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
}
现在我的问题是:
两个版本各有什么优缺点?
您建议保留哪两个?
欢迎任何反馈、建议和意见。
你要反馈这里是我的反馈
你的方法没有任何意义。如果您查看没有包含代码的方法声明。
private Mono<ServerResponse> validateUser(User user)
这没有意义,此方法应该验证用户,但您 return ServerResponse?在我看来,首先应该进行验证,然后 return 某种布尔值或验证错误列表。
我不推荐您提供的任何解决方案,您应该研究并开始使用 Mono.error
而不是 switchIfEmpty
您应该将响应构建与验证逻辑分开。
如果验证规则发生变化会怎样?或者您想要基于验证失败的其他响应?现在他们在一起了。
您已经可以看到您在两个地方 return 发出相同的错误请求,但错误消息不同。重复
这是我的看法,我会怎么做:
- 收到请求
- 将请求映射到用户 (bodyToMono)
- 在一个方法中验证用户 return 一个包含错误数量的列表
- 如果用户验证失败,请检查此列表,如果失败,则将单声道用户映射到包含带有某种错误文本的 illegalArgumentException 的单声道错误。
- 将单声道错误中的异常映射到状态代码
- 如果验证通过,将用户保存在
Mono.doOnSuccess
块中
这对我来说更加清晰和可预测 return 代码和验证逻辑的分离。
根据 Thomas Andolf 的建议以及其他用户的建议,我提出了以下实施方案:
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> findUsers(ServerRequest serverRequest) {
return ok()
.contentType(APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(this::validate)
.flatMap(this::validateEmailNotExists)
.flatMap(this::saveUser)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(newUser))
)
.onErrorResume(ValidationException.class, e -> status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(e.getErrors()))
)
.onErrorResume(DuplicateUserException.class, e -> status(CONFLICT)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(e.getErrorMessage()))
);
}
private Mono<User> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(userMono -> Mono.<User>error(new DuplicateUserException("User already exists")))
.switchIfEmpty(Mono.just(user));
}
private Mono<User> saveUser(User user) {
return userRepository.save(user);
}
private Mono<User> validate(User user) {
AbstractBindingResult errors = computeErrors(user);
return errors.hasErrors() ? Mono.error(new ValidationException(errors.getAllErrors())) : Mono.just(user);
}
private AbstractBindingResult computeErrors(User user) {
AbstractBindingResult errors = new BeanPropertyBindingResult(user, User.class.getName());
userValidator.validate(user, errors);
return errors;
}
}
它依赖于 Mono.error
、自定义异常和 onErrorResume()
运算符。它相当于问题中的两个实现,但更精简。
我有 两个版本 Webflux/Reactor 处理程序 class。此 class 模仿用户注册用例 - 就业务逻辑而言。
class 的第一个版本依赖于 Mono<User>
,而第二个版本使用普通的 User
。
class的第一个版本:这是依赖于Mono<User>
参数的版本链。注意顶级 public 方法 createUser
使用 userMono
.
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
Mono<User> userMono = serverRequest.bodyToMono(User.class).cache();
return validateUser(userMono)
.switchIfEmpty(validateEmailNotExists(userMono))
.switchIfEmpty(saveUser(userMono))
.single();
}
private Mono<ServerResponse> validateUser(Mono<User> userMono) {
return userMono
.map(this::computeErrors)
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private AbstractBindingResult computeErrors(User user) {
AbstractBindingResult errors = new BeanPropertyBindingResult(user, User.class.getName());
userValidator.validate(user, errors);
return errors;
}
private Mono<ServerResponse> validateEmailNotExists(Mono<User> userMono) {
return userMono
.flatMap(user -> userRepository.findByEmail(user.getEmail()))
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(Mono<User> userMono) {
return userMono
.flatMap(userRepository::save)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
}
class的第二个版本:即依赖于User
参数的版本。
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user ->
validateUser(user)
.switchIfEmpty(validateEmailNotExists(user))
.switchIfEmpty(saveUser(user))
.single()
);
}
private Mono<ServerResponse> validateUser(User user) {
return Mono.just(new BeanPropertyBindingResult(user, User.class.getName()))
.doOnNext(err -> userValidator.validate(user, err))
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private Mono<ServerResponse> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(User user) {
return userRepository.save(user)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
}
现在我的问题是:
两个版本各有什么优缺点?
您建议保留哪两个?
欢迎任何反馈、建议和意见。
你要反馈这里是我的反馈
你的方法没有任何意义。如果您查看没有包含代码的方法声明。
private Mono<ServerResponse> validateUser(User user)
这没有意义,此方法应该验证用户,但您 return ServerResponse?在我看来,首先应该进行验证,然后 return 某种布尔值或验证错误列表。
我不推荐您提供的任何解决方案,您应该研究并开始使用 Mono.error
而不是 switchIfEmpty
您应该将响应构建与验证逻辑分开。
如果验证规则发生变化会怎样?或者您想要基于验证失败的其他响应?现在他们在一起了。
您已经可以看到您在两个地方 return 发出相同的错误请求,但错误消息不同。重复
这是我的看法,我会怎么做:
- 收到请求
- 将请求映射到用户 (bodyToMono)
- 在一个方法中验证用户 return 一个包含错误数量的列表
- 如果用户验证失败,请检查此列表,如果失败,则将单声道用户映射到包含带有某种错误文本的 illegalArgumentException 的单声道错误。
- 将单声道错误中的异常映射到状态代码
- 如果验证通过,将用户保存在
Mono.doOnSuccess
块中
这对我来说更加清晰和可预测 return 代码和验证逻辑的分离。
根据 Thomas Andolf 的建议以及其他用户的建议,我提出了以下实施方案:
@Component
@RequiredArgsConstructor
public class UserHandler {
private final @NonNull UserRepository userRepository;
private final @NonNull UserValidator userValidator;
public Mono<ServerResponse> findUsers(ServerRequest serverRequest) {
return ok()
.contentType(APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(this::validate)
.flatMap(this::validateEmailNotExists)
.flatMap(this::saveUser)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(newUser))
)
.onErrorResume(ValidationException.class, e -> status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(e.getErrors()))
)
.onErrorResume(DuplicateUserException.class, e -> status(CONFLICT)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(e.getErrorMessage()))
);
}
private Mono<User> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(userMono -> Mono.<User>error(new DuplicateUserException("User already exists")))
.switchIfEmpty(Mono.just(user));
}
private Mono<User> saveUser(User user) {
return userRepository.save(user);
}
private Mono<User> validate(User user) {
AbstractBindingResult errors = computeErrors(user);
return errors.hasErrors() ? Mono.error(new ValidationException(errors.getAllErrors())) : Mono.just(user);
}
private AbstractBindingResult computeErrors(User user) {
AbstractBindingResult errors = new BeanPropertyBindingResult(user, User.class.getName());
userValidator.validate(user, errors);
return errors;
}
}
它依赖于 Mono.error
、自定义异常和 onErrorResume()
运算符。它相当于问题中的两个实现,但更精简。