在编写反应链时,我应该依赖 "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() 运算符。它相当于问题中的两个实现,但更精简。