反应性存储库

时间:2018-03-15 09:32:15

标签: java spring project-reactor

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

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

的反应式存储库
  1. 保存书。
  2. 查找所有图书。
  3. 我的存储库需要涵盖以下场景:

    情景A:

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

      我能够实现这一目标,但我仍然认为有更简单的方法吗?也许在Reactor项目中有一个已经涵盖这种情况的解决方案?

      我的实施:

      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();
      }
      }
      

      Ofc,我不能只使用Cold发布者 - 因为流将在发布收集的图书后完成。出于同样的原因,我不能使用Hot,因为我会错过在某人订阅之前生成的元素。

      旁注:在我的代码中,我的地图没有任何清理机制,所以它会在某些时候产生异常,但现在这并不重要。

1 个答案:

答案 0 :(得分:0)

这么简单......我不知道为什么我错过了这个漂亮的算子:https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#replay--

所以基本上删除代码的整个let validJsonResponseData = """ {"message" : "A message"} """.data(using: .utf8)! let simpleTextResponseData = "Error".data(using: .utf8)! struct Response: Decodable { let message: String } do { let successResponse = try JSONDecoder().decode(Response.self, from: validJsonResponseData) let errorResponse = try JSONDecoder().decode(Response.self, from: simpleTextResponseData) } catch { if let error = error as? DecodingError { switch error { case .dataCorrupted(_): if let simpleTextResponse = String(data: simpleTextResponseData, encoding: .utf8) { let errorResponse = Response(message: simpleTextResponse) } default: throw error } } else { throw error } } 部分并使用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"); 输出:

publish()

使用1st subscriber: one 1st subscriber: two 2nd subscriber: two 输出:

replay()