【问题标题】:Should I rely on "mono of item" or "plain item" arguments when composing reactive chains?在组成反应链时,我应该依赖“单项”还是“普通项”参数?
【发布时间】:2019-09-08 06:27:42
【问题描述】:

我有 两个版本的 Webflux/Reactor 处理程序类。就业务逻辑而言,此类模仿用户注册用例。

该类的第一个版本依赖于Mono<User>,而第二个版本使用一个普通的User

类的第一个版本:这是依赖于 Mono<User> 参数的版本。注意顶级公共方法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))
            );
    }
}

类的第二个版本:这是依赖于 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))
            );
    }
}

现在我的问题:

  • 这两个版本各有什么优缺点?

  • 您会建议保留两者中的哪一个?

欢迎任何反馈、建议和意见。

【问题讨论】:

    标签: reactive-programming spring-webflux project-reactor


    【解决方案1】:

    你想要的反馈是我的反馈

    你的方法真的没有意义。如果您在没有包含代码的情况下查看方法的声明。

    private Mono&lt;ServerResponse&gt; validateUser(User user)

    这没有意义,这个方法应该验证用户,但你返回一个 ServerResponse?在我看来,应该首先进行验证,并返回某种布尔值或验证错误列表。

    我不推荐您提出的任何解决方案,您应该研究并开始使用Mono.error 而不是使用switchIfEmpty

    您应该将响应构建与验证逻辑分开。

    如果验证规则发生变化会怎样?或者您想要基于验证失败的其他响应?现在他们在一起了。

    您已经可以看到,您在两个地方返回了相同的错误请求,但错误消息不同。重复

    这是我的意见和我会做什么:

    • 接收请求
    • 对用户的映射请求 (bodyToMono)
    • 在将返回包含错误数量的列表的方法中验证用户
    • 如果用户验证失败,请检查此列表,如果失败,则将单声道用户映射到单声道错误,该错误包含带有某种错误文本的非法参数异常。
    • 将单声道错误中的异常映射到状态码
    • 如果验证通过,则将用户保存在 Mono.doOnSuccess 块中

    这对我来说通过返回代码和验证逻辑的分离更加清晰和可预测。

    【讨论】:

    • 感谢您的回复。我将根据您的建议组织一门新课程,并将其发布在此处作为编辑。请多多包涵。
    • 如果我有其他问题,我会在这里发表评论,以便整理新课程。
    • 有一个方面需要考虑:在两种情况下我都需要向客户端返回内容:如果发生验证错误(有关验证错误的信息;不仅仅是字符串消息),或者如果用户已成功保存(创建的用户及其 ID)。因此,我需要依赖 doOnSuccess 之外的另一个构造。异常也不会削减它 - 除非我将验证错误状态添加到异常中......
    • 我尝试但未能实施您的解决方案... :-( 考虑到我之前的评论,您能否建议实施您的解决方案?
    • 我不能为你写一个实现,因为我没有你的代码的完整清晰图片,谁调用了 UserHandler? User 是什么样的,什么样的“验证”你应该在你的 UserHandler 之前的层中进行验证。您的 UserHandler 应该有一个清晰的 Api。像Mono&lt;User&gt; UserHandler#createMono&lt;User&gt; UserHandler#updateMono&lt;User&gt; UserHandler#delete 等。之前的层应该进行所有验证,当验证通过时,只需调用UserHandler#save
    【解决方案2】:

    结合 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() 运算符。它相当于问题中的两个实现,但更精简。

    【讨论】:

      猜你喜欢
      • 2019-11-04
      • 1970-01-01
      • 1970-01-01
      • 2018-07-26
      • 2011-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-07
      相关资源
      最近更新 更多