Phy*_*yzz 4 java spring idioms project-reactor
我继承了使用 Spring 和相关库(包括 Reactor)用 Java 编写的 REST 服务的责任。对于像 REST 调用或数据库操作这样的昂贵操作,代码广泛地将结果包装在 Reactor Mono 中。
有各种各样的事情需要在代码中解决,但不断出现的事情是嵌套flatMaps over Monos 用于昂贵的操作序列,这些操作最终缩进了好几层,变成了难以阅读的混乱。我觉得这特别令人讨厌,因为我来自 Scala,在那里这种使用方式flatMap并没有那么糟糕,因为 for comprehension 语法糖将所有内容保持在大致相同的范围级别,而不是更深入。
到目前为止,除了大规模重构之外,我还没有成功找到一种方法来解决这个问题以使其更具可读性(即使那样我也不知道从哪里开始这样的重构)。
基于代码的匿名示例,(所有语法错误均来自匿名化):
public Mono<OutputData> userActivation(InputData input) {
Mono<DataType1> d1 = service.expensiveOp1(input);
Mono<OutputData> result =
d1
.flatMap(
d1 -> {
return service
.expensiveOp2(d1.foo())
.flatMap(
d2 -> {
if (Status.ACTIVE.equals(d2.getStatus())) {
throw new ConflictException("Already active");
}
return service
.expensiveOp3(d1.bar(), d2.baz())
.flatMap(
d3 -> {
d2.setStatus(Status.ACTIVE);
return service
.expensiveOp5(d1, d2, d3)
.flatMap(
d4 -> {
return service.expensiveOp6(d1, d4.foobar())
});
});
});
})
return result;
}
Run Code Online (Sandbox Code Playgroud)
哎呀。我不喜欢那个片段的一些事情,但我将从最重要的开始 - 嵌套。
嵌套的唯一原因是在(例如)中,expensiveOp5()您需要对d1,d2和的引用d3,而不仅仅是d4- 所以您不能只是“正常”映射,因为您丢失了那些较早的引用。有时可以在特定上下文中重构这些依赖项,因此我会首先检查该路由。
但是,如果不可能或不可取,我倾向于发现flatMap()像这样的深层嵌套调用最好通过组合替换为中间对象。
例如,如果您有一堆类,如下所示:
@Data
class IntermediateResult1 {
private DataType1 d1;
private DataType2 d2;
}
@Data
class IntermediateResult2 {
public IntermediateResult2(IntermediateResult1 i1, DataType3 d3) {
this.d1 = i1.getD1();
this.d2 = i1.getD2();
this.d3 = d3;
}
private DataType1 d1;
private DataType2 d2;
private DataType3 d3;
}
Run Code Online (Sandbox Code Playgroud)
...依此类推,那么您可以执行以下操作:
return d1.flatMap(d1 -> service.expensiveOp2(d1.foo()).map(d2 -> new IntermediateResult1(d1, d2)))
.flatMap(i1 -> service.expensiveOp3(i1).map(s3 -> new IntermediateResult2(i1, d3)))
//etc.
Run Code Online (Sandbox Code Playgroud)
当然,您也可以将调用分解为它们自己的方法以使其更清晰(在这种情况下我可能会建议这样做):
return d1.flatMap(this::doOp1)
.flatMap(this::doOp2)
.flatMap(this::doOp3)
.flatMap(this::doOp4)
.flatMap(this::doOp5);
Run Code Online (Sandbox Code Playgroud)
显然,我在上面使用的名称应该只被视为占位符 - 您应该仔细考虑这些名称,因为这里的良好命名将使反应流的推理和解释更加自然。
除了嵌套之外,该代码中还有两点值得注意:
return Mono.error(new ConflictException("Already active"));而不是显式抛出,因为它更清楚地表明您正在处理Mono.error流中的显式。setStatus()在反应链中途使用可变方法- 这会在以后提出问题。相反,使用类似于with模式的东西来生成一个d2带有更新字段的新实例。然后你可以expensiveOp5(d1, d2.withStatus(Status.ACTIVE), d3)在没收那个 setter 调用的同时调用。| 归档时间: |
|
| 查看次数: |
1295 次 |
| 最近记录: |