离线优先应用中,如何与远程数据库同步数据?

14
我正在开发一个"TODO"应用程序,使用Service Workers缓存请求的响应,以便在用户离线时向用户显示缓存的数据。服务器公开了一个RESTful端点,该端点公开了POST、PUT、DELETE和GET资源的端点。考虑到当用户离线并提交TODO项目时,我将其保存到本地IndexedDB中,但由于没有网络连接,无法向服务器发送此POST请求。对于更新或删除现有TODO项目的PUT、DELETE请求也是如此。
问题:
使用什么模式来在连接重新建立时与RESTful服务器同步待处理请求?
2个回答

11
当连接重新恢复时,使用哪些模式来与REST-ful服务器同步挂起的请求?这种情况下Background Sync API将是合适的选择。它使Web应用程序能够在后台同步数据。使用它可以推迟操作直到用户有可靠的连接,确保用户想要发送的任何内容都被发送。即使用户导航离开或关闭浏览器,该操作也会执行,如果需要,您可以通知用户。由于正在保存到IndexDB,因此可以在用户添加、删除或更新TODO项目时注册同步事件。
function addTodo(todo) {
  return addToIndeDB(todo).then(() => {
    // Wait for the scoped service worker registration to get a
    // service worker with an active state
    return navigator.serviceWorker.ready;
  }).then(reg => {
    return reg.sync.register('add-todo');
  }).then(() => {
    console.log('Sync registered!');
  }).catch(() => {
    console.log('Sync registration failed :(');
  });
}

你注册了一个同步事件,类型为add-todo,在service-worker中监听该事件,当收到该事件后,从IndexDB检索数据并进行POST到你的Restful API。
self.addEventListener('sync', event => {
if (event.tag == 'add-todo') {
      event.waitUntil(
      getTodo().then(todos => {
        // Post the messages to the server
        return fetch('/add', {
          method: 'POST',
          body: JSON.stringify(todos),
          headers: { 'Content-Type': 'application/json' }
        }).then(() => {
          // Success!
        });
      })
      })
    );
   }
});

这只是使用后台同步实现它的示例。请注意,您必须在服务器上处理冲突解决。
您可以在客户端使用PouchDB,并在服务器上使用Couchbase或CouchDB。使用PouchDB客户端,您可以在客户端保存数据,并设置自动同步/复制数据,以便每当用户在线时都会进行同步。当数据库同步并存在冲突更改时,CouchDB将检测到此情况,并将用特殊属性“_conflicts:true”标记受影响的文档。它确定哪一个应该作为最新版本,并将其他版本保存为该记录的上一个版本。它不尝试合并冲突的修订。在您的应用程序中,由您来决定如何进行合并。这与Couchbase并没有太大区别。有关冲突解决的更多信息,请参见下面的链接。

我使用过PouchDB和Couchbase / CouchDB / IBM Cloudant,但是我是通过Hoodie来完成的。它具有开箱即用的用户身份验证、处理冲突管理等功能,类似于您的后端。在您的TODO应用程序中,Hoodie将非常适合。我写了一些关于如何使用Hoodie的东西,请查看以下链接:


5

目前我能想到两种方法,取决于您在后端使用的存储选项。

如果您正在使用RDBMS备份所有数据:

在这种方法中离线优先系统面临的问题是可能在发布新数据或更新现有数据时遇到冲突。

为了避免发生冲突,首先必须为客户端的所有对象生成唯一的ID,并确保它们在发布到服务器并保存在数据库中时仍然是唯一的。为此,您可以安全地依靠 UUIDs 为对象生成唯一的ID。 UUID 在分布式系统中保证系统间的唯一性,并且根据您的实现语言,您将拥有无需任何麻烦即可生成UUID的方法。

设计本地数据库,以便您可以将UUID用作本地数据库中的主键。在服务器端,您可以同时拥有自动递增和索引的整数类型的主键和VARCHAR类型来保存UUID。服务器上的主键唯一标识该表中的对象,而UUID唯一标识跨表和数据库的记录。

在同步时将对象发布到服务器时,您只需要检查是否已经存在具有UDID的对象,并从那里采取适当的措施。当您从服务器获取对象时,请同时发送表中对象的主键和对象的UDID。这样,当您将响应序列化为模型对象或将其保存在本地数据库中时,您可以告诉已同步的对象与未同步的对象之间的区别,因为需要同步的对象在本地数据库中不会有主键,只有UUID。
可能出现这样一种情况:当您进行同步时,服务器发生故障并拒绝保存数据。在这种情况下,您可以在对象中保留一个整数变量,用于记录尝试同步它的次数。如果此数字超过某个值,比如3,您将继续同步下一个对象。现在,您对未同步的对象要采取什么措施取决于您对此类对象的政策,作为解决方案,您可以舍弃它们或仅在本地保留它们。 如果您没有使用关系型数据库 作为另一种方法,与其保留所有对象,你可以保留每个客户端在服务器上本地执行的交易记录。每个客户端仅同步交易记录,在提取时通过自下而上地处理所有交易记录来获取当前状态。这与Git使用的方式非常相似。它以交易记录的形式保存您的存储库中的更改,例如添加(或删除)了什么以及由谁进行了更改。每个用户的存储库的当前状态都是从交易记录中计算出来的。这种方法不会导致冲突,但正如你所看到的,开发起来有点棘手。

感谢Ankur的解释,非常感谢您花费的时间。我们计划在这个项目中使用MySQL(关系型数据库管理系统)作为我们的数据库。 - daydreamer
我正在将离线数据存储在IndexDB中,但当用户联网时,我想将该数据存储到远程数据库中,请问如何实现? - Muhammad Usama Mashkoor
Ankur,听起来UUID的生成必须在客户端上进行,这是真的吗?这意味着它们没有数据库索引,因此在第一次访问后端时尚未同步,对吗? - Ryan Weiss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接