这篇文章是基于对使用 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。
如果有任何疑问,欢迎交流哦~