Java 反应式存储库

Java 反应式存储库,java,spring,project-reactor,Java,Spring,Project Reactor,我尝试创建一些基于反应堆栈(Reactor+WebFlux)的基本Spring5应用程序 我的下一个目标是实现能够: 存书 找到所有的书 我的存储库需要涵盖以下场景: 情景A: 没有人订阅FindAll 某人保存了一本书(id=1) 客户1订阅FindAll Book(id=1)被推送到Client1(Client1保持订阅状态,流未完成!) 某人保存了一本书(id=2) Book(id=2)被推送到Client1(Client1保持订阅状态,流未完成!) 因此,在我看来,这个场景是冷源和热源概

我尝试创建一些基于反应堆栈(Reactor+WebFlux)的基本Spring5应用程序

我的下一个目标是实现能够:

  • 存书
  • 找到所有的书 我的存储库需要涵盖以下场景:

    情景A:

  • 没有人订阅FindAll
  • 某人保存了一本书(id=1)
  • 客户1订阅FindAll
  • Book(id=1)被推送到Client1(Client1保持订阅状态,流未完成!)
  • 某人保存了一本书(id=2)
  • Book(id=2)被推送到Client1(Client1保持订阅状态,流未完成!)
  • 因此,在我看来,这个场景是冷源和热源概念的混合。在任何人订阅之前,我们收集有人保存在我们存储库中某个缓冲区中的数据(比如普通列表)。对于将订阅FindAll的所有订阅者,我们需要推送缓冲列表(在其订阅之前收集的),并且不完成流以允许推送以后的收集更新

    我能够做到这一点,但我仍然在想有没有更简单的方法来做到这一点?也许在反应堆项目中有一个解决方案已经涵盖了这个场景

    我的实施:

    public class InMemoryBookRepository {
    
    private final Map<String, Book> bookMap = new ConcurrentHashMap<>();
    private final UnicastProcessor<Book> processor = UnicastProcessor.create();
    private final FluxSink<Book> fluxSink = processor.sink(FluxSink.OverflowStrategy.LATEST);
    private final Flux<Book> hotFlux = processor.publish().autoConnect();
    
    @Override
    public void save(Book book) {
        bookMap.put(book.getId(), book);
        fluxSink.next(book);
    }
    
    @Override
    public Flux<Book> findAll() {
        //without fromIterable I cannot push books that where saved before someone subscribed
        return Flux.fromIterable(bookMap.values())
                .concatWith(hotFlux)
                //Unfortunately this solution produces duplicates so we need to filter them
                .distinct();
    }
    }
    
    MemoryBookRepository中的公共类{
    private final Map bookMap=新ConcurrentHashMap();
    专用最终UnicastProcessor=UnicastProcessor.create();
    private final FluxSink FluxSink=processor.sink(FluxSink.OverflowStrategy.LATEST);
    私有最终流量hotFlux=processor.publish().autoConnect();
    @凌驾
    公共作废保存(书籍){
    bookMap.put(book.getId(),book);
    fluxSink.next(book);
    }
    @凌驾
    公共流量findAll(){
    //如果没有fromIterable,我就不能在别人订阅之前把保存的书推到哪里
    返回Flux.fromIterable(bookMap.values())
    .concatWith(热通量)
    //不幸的是,此解决方案会产生重复项,因此我们需要对其进行筛选
    .distinct();
    }
    }
    
    Ofc,我不能只使用Cold publisher,因为流将在出版藏书后完成。出于同样的原因,我不能使用Hot-one,因为我会错过在别人订阅之前生成的元素


    旁注:在我的代码中,我的映射没有任何清理机制,因此它会在某个时候产生异常,但现在这并不重要。

    如此简单。。。我不知道为什么我错过了这个漂亮的接线员:

    因此,基本上删除整个
    List/Map
    部分代码,并使用
    replay()
    而不是
    publish()

    简化示例:

    UnicastProcessor<String> processor = UnicastProcessor.create();
    FluxSink<String> fluxSink = processor.sink(FluxSink.OverflowStrategy.LATEST);
    //change 'publish()' to 'replay()'
    Flux<String> hotFlux = processor.publish().autoConnect(); 
    
    hotFlux.subscribe(n -> log.info("1st subscriber: {}", n));
    
    fluxSink.next("one");
    
    hotFlux.subscribe(n -> log.info("2nd subscriber: {}", n));
    
    fluxSink.next("two");
    
    使用
    replay()
    进行输出:


    只是想知道为什么不能使用像mongo这样的反应数据库?好问题。但答案很简单——这个存储库是真实系统模拟的一部分。我不想为了模拟的目的而在我的应用程序中添加更多需要单独维护的组件。然而,如果有任何反应式数据库的内存解决方案,我一定会尝试一下。有吗?
    1st subscriber: one
    1st subscriber: two
    2nd subscriber: two
    
    1st subscriber: one
    2nd subscriber: one
    1st subscriber: two
    2nd subscriber: two