将异常作为反应流

时间:2017-03-16 18:50:13

标签: reactive-programming project-reactor

我有一个反应流,我想要一步应用验证检查,如果失败,将抛出异常。是否有一种普遍接受的风格?据我所知,我在Monothen()filter()中有三个选项(使用map())。

  1. filter()最接近我想要的流程,因为我实际上并没有尝试更改流中的数据类型或切换到另一个流。但是,filter应该返回true / false来过滤掉项目,所以总是返回TRUE有点傻。
  2. then()让我专门选择错误/成功排放,但有时候对于这种类型的验证,我无法轻易地将其拆分为自己的私有方法,并且样板文件使流声明更容易阅读。 / LI>
  3. map()与使用filter()几乎相同,只是您总是在输入中返回而不是TRUE。
  4. 作为一个非常人为的例子,考虑一个包含0个或更多字母的服务发送给某个人的服务:

    public interface Person {
        UUID getId();
        List<String> getKnownLanguages();
    }
    
    public interface Letter {
        String getLanguage();
    }
    
    public class LetterService {
        private Letter findOneLetterForPerson(final UUID id) { /* ... */ }
    
        private void removeLetter(final Letter letter) { /* ... */ }
    }
    

    创建如下所示的方法的最佳选择是什么:

    public Mono<Optional<Letter>> getNextValidLetterForPerson(final Person person) {
        return Mono.just(person)
                   .and(this::getNextLetterForPerson)
    
                   /////////////////////////////////////////
                   //
                   .filter(this::validatePersonCanReadLetter1)
                   .map(Tuple2::getT2)
                   //
                   // OR
                   //
                   .then(this::validatePersonCanReadLetter2)
                   //
                   // OR
                   //
                   .map(this::validatePersonCanReadLetter3)
                   //
                   /////////////////////////////////////////
    
                   // If the letter was invalid for the person, remove the letter from the
                   // the system as a side effect, and retry retrieving a letter to send
                   .doOnError(this::removeInvalidLetter)
                   .retry(this::ifLetterValidationFailed)
    
                   // Map the result to an appropriate Optional
                   .map(Optional::of)
                   .defaultIfEmpty(Optional.empty());
    }
    

    以上示例中使用的支持方法是:

    public static class LetterInvalidException extends RuntimeException {
        private Letter mLetter;
        public LetterInvalidException(final Letter letter) { mLetter = letter; }
        public Letter getLetter() { return mLetter; }
    }
    
    
    /** Gets the next letter for a person, as a reactive stream */
    private Mono<Letter> getNextLetterForPerson(final Person person) {
        return Mono.create(emitter -> {
            final Letter letter = mLetterService.findOneLetterForPerson(person.getId());
    
            if (letter != null) {
                emitter.success(letter);
            }
            else {
                emitter.success();
            }
        });
    }
    
    /** Used to check whether the cause of an error was due to an invalid letter */
    private boolean ifLetterValidationFailed(final Throwable e) {
        return e instanceof LetterInvalidException;
    }
    
    /** Used to remove an invalid letter from the system */
    private void removeInvalidLetter(final Throwable e) {
        if (ifLetterValidationFailed(e)) {
            mLetterService.removeLetter(((LetterInvalidException)e).getLetter());
        }
    }
    
    /*************************************************************************
     *
     *************************************************************************/
    
    private boolean validatePersonCanReadLetter1(final Tuple2<Person, Letter> tuple) {
        final Person person = tuple.getT1();
        final Letter letter = tuple.getT2();
    
        if (!person.getKnownLanguages().contains(letter.getLanguage())) {
            throw new LetterInvalidException(letter);
        }
    
        return true;
    }
    
    private Mono<Letter> validatePersonCanReadLetter2(final Tuple2<Person, Letter> tuple) {
        return Mono.create(emitter -> {
            final Person person = tuple.getT1();
            final Letter letter = tuple.getT2();
    
            if (!person.getKnownLanguages().contains(letter.getLanguage())) {
                emitter.error(new LetterInvalidException(letter));
            }
            else {
                emitter.success(letter);
            }
    
        });
    }
    
    private Letter validatePersonCanReadLetter3(final Tuple2<Person, Letter> tuple) {
        final Person person = tuple.getT1();
        final Letter letter = tuple.getT2();
    
        if (!person.getKnownLanguages().contains(letter.getLanguage())) {
            throw new LetterInvalidException(letter);
        }
    
        return letter;
    }
    

    理想情况下,我会喜欢Mono<T> validate(..)这样的方法,它允许测试流项并返回或抛出异常(如果返回,框架会将其视为错误),但我相当新反应式编程并没有看到任何类似的工作。

1 个答案:

答案 0 :(得分:3)

也许handle是一个更好的解决方案,可以作为地图和过滤器的组合使用:

Mono.just(p).and(test::getNextLetterForPerson).handle((tuple, sink) -> {
    final Person person = tuple.getT1();
    final Letter letter = tuple.getT2();

    if (!person.getKnownLanguages().contains(letter.getLanguage())) {
        sink.error(new LetterInvalidException(letter));
        return;
    }

    sink.next(letter);
}).subscribe(value -> System.out.println(((Letter) value).getLanguage()),
t -> System.out.println(t.getMessage()));

正如您所看到的,它几乎就像您的validatePersonCanReadLetter3