在 Spring Reactor 和 Kotlin 中处理空值的惯用方法是什么?

What is the idiomatic way to work with nulls in Spring Reactor and Kotlin?

我有一个字符串通量,应该将其转换为 dto 通量。解析可能会因错误而结束,根据业务规则,我只需要跳过此类条目

如果我使用“Kotlin 的”null - 我得到了 NPE,因为按照设计,reactor 不接受 .map 中的 null

fun toDtoFlux(source:Flux<String>):Flux<Dto>{
    source.map(Parser::parse)
          .filter(it!=null)
}

object Parser{
   fun parse(line:String):Dto?{
   ..
 }
}

我可以使用可选的。但这不是 Kotlin 的方式。

fun toDtoFlux(source:Flux<String>):Flux<Dto>{
    source.map(Parser::parse)
          .filter(Optional.isPresent)
          .map(Optional::get)
}

object Parser{
   fun parse(line:String):Optional<Dto>{
   ..
 }
}

在 Kotlin 中处理此类情况最惯用的方法是什么?

我看到的解决方案:

使用 Reactor API

我建议您使用 Reactor API 来解决这种情况,并使您的解析器 return 成为 Mono。空的 Mono 表示没有结果。这样,您就可以使用 flatMap 而不是链接 map/filter/map.

这可能看起来有点矫枉过正,但如果需要的话,它将允许任何解析器实现在未来做异步的事情(从第三方服务获取信息,等待用户验证等)。

并且它还提供了一个强大的 API 来管理解析错误,因为您可以定义 backoff/custom 解析结果的错误策略。

那会改变你的例子:

fun interface Parser {
   fun parse(record: String): Mono<Dto>;
}

fun Parser.toDtoFlux(source:Flux<String>): Flux<Dto> {
    source.flatMap(this::parse)
}

使用密封class

受函数式编程的启发,Kotlin 提供了其他管理结果选项的方法。一种方法是使用密封的 classes 来设计一组常见情况以在解析时处理。它允许对丰富的结果进行建模,为解析器用户提供多种选择来处理错误。

sealed class ParseResult

class Success(val value: Dto) : ParseResult
class Failure(val reason : Exception) : ParseResult
object EmptyRecord : ParseResult

fun interface Parser {
    fun parse(raw: String) : ParseResult
}

fun Parser.toDtoFlux(source:Flux<String>): Flux<Dto> {
    return source.map(this::parse)
                 .flatMap { when (it) { 
                    is Success -> Mono.just(it.value)
                    is Failure -> Mono.error(it.reason) // Or Mono.empty if you don't care
                    is EmptyRecord -> Mono.empty()
                 }}
}

您可以创建一个扩展函数:

fun <T, U> Flux<T>.mapNotNull(mapper: (T) -> U?): Flux<U> =
    this.flatMap { Mono.justOrEmpty(mapper(it)) }

那么你可以这样使用它:

fun main() {
    Flux.just("a", "b", "c")
        .mapNotNull { someNullableMapFunction(it) }
        .doOnNext { println(it) } // prints "a" and "c"
        .blockLast()
}

fun someNullableMapFunction(it: String): String? {
    if (it == "b") {
        return null
    }

    return it
}

更新

基于 Simon 的评论扩展功能实现在 Reactor 中可能更符合习惯(和性能?):

fun <T, U> Flux<T>.mapNotNull(mapper: (T) -> U?): Flux<U> =
    this.handle { item, sink -> mapper(item)?.let { sink.next(it) } }