在 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) } }
我有一个字符串通量,应该将其转换为 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) } }