Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
3.3k views
in Technique[技术] by (71.8m points)

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

I have a Flux of strings that should be converted to a Flux of dto. Parsing can be finished with an error and by the business rules I just need to skip such entries

If I use "Kotlin's" null - I got NPE because by design reactor doesn't accept nulls in .map

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

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

I can use Optional. But it is not a Kotlin way.

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>{
   ..
 }
}

What is the most idiomatic way to handle such cases in Kotlin?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The solutions I see :

Using Reactor API

I'd suggest you to use Reactor API to address such case, and make your parser return a Mono. The empty Mono represents the absence of result. With that, you can use flatMap instead of chaining map/filter/map.

It may seem a little overkill like that, but it will allow any parser implementation to do async stuff in the future if needed (fetching information from third-party service, waiting validation from user, etc.).

And it also provide a powerful API to manage parsing errors, as you can define backoff/custom error policies on parsing result.

That would change your example like that :

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

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

Using sealed class

Kotlin offers other ways of managing result options, inspired by functional programming. One way is to use sealed classes to desing a set of common cases to handle upon parsing. It allows to model rich results, giving parser users multiple choices to handle errors.

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()
                 }}
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...