在一个中等复杂的用例中,策略处理和使订阅的缓存数据失效

Posted

技术标签:

【中文标题】在一个中等复杂的用例中,策略处理和使订阅的缓存数据失效【英文标题】:Strategy handling and invalidating cached data on subscriptions in a moderately complex usecase 【发布时间】:2020-09-06 18:07:44 【问题描述】:

让我们以聊天应用程序为例。 用户可以访问多个聊天线程,每个线程都有多条消息。 用户界面由左侧的线程列表 (listThreads) 组成,其中包含对方的名称、最后一条消息、未读消息计数和最后一条消息的日期和时间,以及实际消息 ( viewThread) 和右侧的回复框(想想 facebook messenger)。

当用户选择一个消息线程时,viewThread 组件订阅一个查询,内容如下:

  query thread 
    threads(id: 'xxxx') 
      id
      other_party  id name 
      unread_messages
      messages  
        sent_by  id  
        timestamp
        text
      
  

为了实时更新,它设置q.subscriptToMore 订阅如下:

  subcription newMessages 
    newMessage(thread_id: 'xxx') 
        sent_by  id  
        timestamp
        text    
    
  

这非常有效,新消息会按应有的方式显示。

要列出可用的消息线程,查询所有线程的不太详细的视图:

  query listThreads 
    threads 
      id
      other_party  id name 
      unread_messages
      last_updated_at
    
  

为了使列表保持同步,使用了相同的订阅,没有过滤thread_id,并且线程列表数据是手动更新的

这也很好用。

但是,如果选择了线程A,则会缓存线程A 的消息。 如果之后选择线程B,则订阅获取线程A的详细信息的查询将被销毁,因为当路由器撤消viewThread组件时,observable被销毁。

如果在用户查看线程 B 时消息到达线程 A,则更新 threadList(因为订阅是实时的),但如果用户切换回线程 A,则消息是从缓存中加载的,这些缓存现在已经过时了,因为没有订阅会更新缓存或使缓存失效的特定消息线程。

在用户导航到完全不同的页面的其他情况下,线程列表不会显示在视图中,问题更加明显,因为没有任何与积极订阅的聊天消息相关的内容,因此没有什么可以使无效新消息到达时缓存的数据,尽管理论上服务器通过提供新消息订阅事件来提供一种方法。

我的问题如下:

保持 Apollo 缓存的数据同步/无效的最佳做法是什么? 保持嵌套数据同步的最佳实践是什么(事件线程的消息 [见下文])。我觉得不必在事件查询中实现有关如何订阅和更新消息数据的逻辑是一个干净的解决方案。

使用.subscribeToMore 可以使活跃使用的数据保持同步,但是一旦不再使用该查询,数据就会保留在缓存中,随着时间的推移可能会或可能不会过时。当可观察对象超出范围时,有没有办法删除缓存的数据?只要至少有一个查询使用它,就保持这些数据缓存,因为我相信它还实现了基于服务器推送事件保持同步的逻辑。

是否应该使用订阅(通过 SPA 的整个生命周期)所有订阅事件并包含有关如何更新每种类型的缓存数据(如果存在于缓存中)的知识的服务? (可以通知该服务哪些数据需要保持同步以避免使用不必要的资源)(如在订阅所有newMessage 事件并基于此戳缓存的服务中)?这会自动为返回的对象引用此类数据的查询发出新值吗? (更新message:1 是否会使在其消息字段中返回相同message:1 的线程查询自动发出一个新值)或者这些查询也必须手动更新?

当扩展这个模型时,这开始变得非常麻烦,比如Events也有自己的聊天线程,所以现在查询event thread messages ... 需要订阅newMessage订阅,这打破了封装和单一责任原则。 订阅newMessage 数据也是有问题的,需要提供与事件关联的消息线程的ID,但在查询返回之前这是未知的。由于这个.subscribeToMore 无法使用,因为那时我还没有thread_id 可用。

【问题讨论】:

请正确format你的代码块和内联代码,以提高这篇文章的可读性。 @DanielRearden 对此感到抱歉,不知道为什么我的格式被删除了。 @ÁkosVandra - 你有没有找到解决方案?如果是这样,你可以在这里发布吗?一年零七个月后,我面临着同样的事情。 @JustinHandley 我暂时停止了这个必要的个人项目 - 暂停时间比预期的要长,哈哈,但不幸的是,我不记得找到真正的解决方案。我想我尝试了新的 graphql api 并在那里找到了一个 hacky 解决方案,但我不确定,抱歉。如果您最终找到了解决方案,如果您能发布它,我将不胜感激,这样当我终于可以回到我的项目时,我可以使用它。成为父亲会改变优先事项:) 【参考方案1】:

如果预期的行为是“每次我打开一个线程时,显示最新消息,而不仅仅是缓存的内容”,那么您只需将您的 thread 查询的 fetchPolicy 设置为 network-only,这将确保请求始终发送到服务器,而不是从缓存中完成。 apollo-angular 的文档缺少有关此选项的信息,但这是来自 React docs 的描述:

有效的 fetchPolicy 值为:

cache-first:这是默认值,我们总是先尝试从缓存中读取数据。如果完成查询所需的所有数据都在缓存中,则将返回该数据。如果缓存的结果不可用,Apollo 只会从网络中获取。此获取策略旨在最大限度地减少渲染组件时发送的网络请求数。 cache-and-network:此获取策略将让 Apollo 首先尝试从您的缓存中读取数据。如果完成查询所需的所有数据都在缓存中,则将返回该数据。但是,无论完整数据是否在您的缓存中,此 fetchPolicy 将始终使用网络接口执行查询,这与 cache-first 不同,后者仅在查询数据不在您的缓存中时才会执行您的查询。此获取策略优化了用户获得快速响应,同时还尝试以额外的网络请求为代价使缓存数据与您的服务器数据保持一致。 network-only:这个获取策略永远不会从缓存中返回初始数据。相反,它将始终使用您的网络接口向服务器发出请求。此获取策略优化了与服务器的数据一致性,但代价是在可用时立即响应用户。 cache-only:此获取策略永远不会使用您的网络接口执行查询。相反,它总是会尝试从缓存中读取。如果缓存中不存在查询的数据,则会引发错误。此获取策略允许您仅与本地客户端缓存中的数据进行交互,而无需发出任何网络请求,从而使您的组件保持快速,但这意味着您的本地数据可能与服务器上的数据不一致。如果您只想与 Apollo 客户端缓存中的数据进行交互,请务必查看 ApolloClient 实例上可用的 readQuery() 和 readFragment() 方法。 no-cache:此获取策略永远不会从缓存中返回您的初始数据。相反,它将始终使用您的网络接口向服务器发出请求。与仅网络策略不同,它也不会在查询完成后将任何数据写入缓存。

【讨论】:

虽然这可以解决问题,但它不是解决方案。每次关闭缓存并从服务器获取数据是次优的,缓存的目的应该是避免这种情况。这也意味着每个查询都必须知道缓存中是否有过时的数据,除非缓存完全关闭。我正在寻找一种将订阅用于其原始目的的方法:保持缓存数据同步,或使其无效。恕我直言,如果服务器提供了一种方式,缓存永远不应该存储过时的信息。

以上是关于在一个中等复杂的用例中,策略处理和使订阅的缓存数据失效的主要内容,如果未能解决你的问题,请参考以下文章

AppSync 的订阅是不是仅限于一个特定的用例?

MySql JSON 数据类型的用例

UML-用例关联

xmpp ejabberd - 查询用户存在

涉及多部手机的用例的自动化移动测试策略 - 例如蓝牙数据传输等

如何在订阅Angular7单元测试用例中对代码进行单元测试