0

我们遇到了以下行为,但我们希望知道它是否是预期的,以及是否有兴趣将它记录为一些有种陷阱。春季数据MongoDB save()结果双重插入

我们与春天启动2 /春WebFlux试验和设置了一个小的应用程序,主要有这样的事情(所有缩短):

@PostMapping 
public Mono<Todo> addTodos(@RequestBody Person person) { 
    return personService.addPerson(person); 
} 

服务第一这个样子,因为我们希望还出版人另外的事件到消息队列:

public class PersonService { 
    public Mono<Person> addPerson(Person person) { 
     Mono<Person> addedPerson = personRepository.save(person); 
     addedPerson.subscribe(p -> rabbitTemplate.convertAndSend("persons", p)); 
     return addedPerson; 
    } 
} 

所以,这显然是错误的,那样做。 .subscribe()触发流程,我们假设反应REST控制器在序列化响应数据之前在后台执行相同操作,从而产生第二个并行流程。最后,我们在数据库中的persons集合中结束了两个重复条目。

经过这么长时间的介绍后,终于提出了这样一个问题:这是多个用户触发多个插入的预期行为(基本上,如果您订阅n次,您将得到n插入)?

如果是,这可能是初学者需要强调的一个陷阱,特别是如果我们的理解是正确的,那么反应式REST控制器会在屏幕下执行.subscribe()

回答

4

你来到了描述预期行为的结论。

反应式编程模型与各个领域的命令式编程模型不同。

命令式编程结合了转换,映射,执行和其他方面。您可以通过创建条件/循环流,可以返回值并将值传递给API调用的方法调用来表达这些。

反应式编程解耦的什么它是如何将要执行的发生的声明。使用反应式基础设施的执行分为两部分:反应式序列组成和实际执行。在你的代码中,你只编写反应序列。执行发生在您的代码之外。

当您撰写Publisher时,则生成的Publisher包含对执行后会发生的事情的声明。 A Publisher并不意味着它是否将首先执行,也不意味着最终订阅的订阅者数量。

从上方拍摄的例子中,Mono<Person> PersonRepository.save(…)返回一个发布商:从PersonDocument

    1. 地图数据保存Document到的MongoDB和
    2. 发射保存Person一旦从MongoDB的一个响应来返回

    这是一个使用特定存储库保存数据的配方方法。创建发布者不会执行发布者,发布者也不会对执行次数置之不理。多次拨打.subscribe()多次执行发布者。我想说.subscribe()不是一个陷阱。一种反应式编程模型方法将您的执行放在了一边。如果您拨打.subscribe().block(),那么您应该有充分的理由这样做。每当您在代码中看到.subscribe().block()时,您应该特别注意这是否正确。您的执行环境负责订阅Publisher


    几个观察结果:

    • RabbitTemplate是阻塞API。你不应该混合反应和阻塞的API。如果您没有其他选择,则将阻止呼叫转移给工作人员。在包含阻塞工作的实际运营商之前,沿着Scheduler使用publishOn(…),或者使用ExecutorService/CompletableFuture以及flatMap(…)
    • 使用flatMap(…)运营商的反应流程组成Mono/FluxflatMap(…)操作员启动最终完成并继续流程的非阻塞子流程。
    • 使用doOnXXX(…)运营商(doOnNext(…),doOnSuccess(…),...)为回发时发布者发出特定信号。这些钩子方法允许方便地拦截非阻塞消耗的元素。

    参考文献:

  • +0

    非常感谢那精心设计的答案!我的关键是“如果你调用'subscribe()''你应该有一个很好的理由”。我并不是全新的反应式编程,但项目反应器中的术语和最佳实践对我来说是全新的。 也感谢操作员概述!不知怎的,错过了。 –