这篇文章是基于对使用 spring webflux 编写代码的仓库的观察。

在正确的时间,使用正确的工具,用正确的方式,去处理正确的工作。

Spring webflux 是一个用于提供响应式编程能力的框架。

map 和 flatMap 的区别

是的 map 和 flatMap 是有区别的,这些区别不太容易注意得到。

flatMap 应该被用于非阻塞的操作,简单的说,就是返回Mono或者Flux类型的数据。

map 应该被用于固定时间内转换数据的操作,它是同步完成的。

例子:我们使用 flatMap 把数据存到数据库中,因为它是一个执行时间不确定的异步事件。而把 Person 转成 EnhancedPerson,我们使用了 map,因为它是一个每次执行时间都相对确定的同步事件。

1
2
3
4
5
6
return Mono.just(Person("name", "age:12"))
        .map { person ->
            EnhancedPerson(person, "id-set", "savedInDb")
        }.flatMap { person ->
            reactiveMongoDb.save(person)
        }

使用嵌套的 map/flatMap

大多数有命令式编程观念的程序员,都会在 map/flatMap 中编写大量的代码,这不是一个好习惯,因为它让代码变得难以阅读,就像下面这个嵌套了两个 flatMap 的例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun makePersonASalariedEmployee(personId: String): Mono<Person> {

    return personsRepository.findPerson(personId)
            .flatMap { person ->
                employeeService.toEmployee(person)
                        .flatMap { employee ->
                         
salariedEmployeService.toSalariedEmployee(employee)
                        }
            }
}

unix 哲学:

Do One Thing and Do It Well

这句话用 flatMap 来解释的话,就是在每个 map 中,只执行一次转换。当需要多次转换的时候,调用多次 map,形成一个调用链来处理数据流。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun makePersonASalariedEmployee(personId: String): Mono<Person> {

    return personsRepository.findPerson(personId)
            .flatMap { person ->
                employeeService.toEmployee(person)
            }
            .flatMap { employee ->
                salariedEmployeService.toSalariedEmployee(employee)
            }
}

不使用反应式函数

很多命令式编程,都可以用这些强大的链式函数替换掉,这样可以使代码更加清晰。这个例子就使用了 filter 来去掉 if。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
return Mono.just(Person("name", false))
        .flatMap { person ->
            if (person.consentToSave)
                reactiveMongoDb.save(person)
            Mono.empty()

        }
...The above code..........|........can be transformed............
...elegantly...............|.........using filter function........
....as shown ..............V.......in..the below code............
return Mono.just(Person("name", false))
        .filter { person ->
            person.consentToSave
        }
        .flatMap { person ->
            reactiveMongoDb.save(person)
        }

当心 switchIfEmpty 函数

总的来说,switchIfEmpty 函数中的操作也会执行。所以一定要注意,在 switchIfEmpty 中执行的代码,一定是没有副作用的。例如下面这个方法,数据一样会存到数据,因为 switchIfEmpty 判断的是代码的结果。

1
2
3
4
return ordersDbRepo.findById("order-id")
        .switchIfEmpty(
                missingOrdersDb.save(Order("order-id", false))
        )

一种方法是使用 defer。

1
2
3
return ordersDbRepo.findById("order-id")
        .switchIfEmpty(
                Mono.defer { missingOrdersDb.save(Order("order-id", false)) })

要尽可能的使用响应式组件

当代码中使用的响应式组件越多,响应式编程带来的好处就越明显。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Flux.range(1, 10)
            .map {
                doSomeIoOperation(it)
            }
fun doSomeIoOperation(number: Int): Int {
//  some io operation
}
*******The below doSomeIoOperation expects IO to be an asyc task and should be the preferred way of writing in reactive way****
Flux.range(1, 10)
            .flatMap {
                doSomeIoOperation(it)
            }
fun doSomeIoOperation(number: Int): Mono<Int> {
//  some io operation
}

面向接口,不是实现

虽然现在webflux还不成熟,无法以非阻塞方式支持所有异步操作,但是如果我们可以将代码改为 Publishers 风格,就会在未来的集成中更加轻松。

另一个更好的选择是在发送http请求时,使用webClient(reactive),而不是restTemplate。

如果有任何疑问,欢迎交流哦~