同时使用localStorage和REST远程服务器的数据层架构

12

有没有任何想法或参考资料,关于如何实现同时使用localStorage和REST远程存储的数据持久层:

某个客户端的数据是使用localStorage存储的(使用一个ember-data indexedDB适配器)。本地存储的数据与远程服务器同步(使用ember-data REST适配器)。

服务器从所有客户端收集所有数据。使用数学集合符号(set theory)

Server = Client1 ∪ Client2 ∪ ... ∪ ClientN 

通常情况下,任何记录可能不会对某个特定客户端唯一:

ClientX ∩ ClientY ≠ 0,  ∀ X,Y ∈ [1,N]

以下是几种情况:

  • 客户端创建一条记录。无法在客户端上设置记录的id,因为这可能与服务器上存储的记录冲突。因此,新建的记录需要提交到服务器 -> 接收id -> 在localStorage中创建记录。

  • 记录在服务器上更新,导致localStorage和服务器中的数据不同步。只有服务器知道这一点,因此架构需要实现一个推送架构 (?)

你会使用2个存储(一个用于localStorage,一个用于REST)并在它们之间进行同步,还是使用一个混合indexedDB / REST适配器,并在适配器内编写同步代码?

您能想到任何避免实现推送(Web Sockets,...)的方法吗?


本文提供了一些见解:http://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps - Panagiotis Panagi
2个回答

7
你提出的问题无法简单地用几段话回答或解决。尽管如此,以下是我的尝试...
首先,你所采用的方法存在一些困难:
1.客户端必须始终连接到网络才能创建数据并从服务器接收密钥。 2.如果您确实创建了不同的存储(本地存储和REST),则需要查看两个存储中的所有应用程序代码,这显着增加了应用程序的每个部分的复杂性。 3.创建行后,如果要创建子行,则必须等待服务器返回主键,然后才能将其用作子行中的外键。对于任何中等复杂的数据结构,这都成为了沉重的负担。 4.当服务器关闭时,所有客户端都无法创建数据。
这是我的方法。它使用SequelSphereDB,但大多数概念可以在其他客户端数据管理系统中重用。
首先:使用UUID作为主键。
大多数客户数据管理系统应该提供一种生成通用唯一标识符的方法。SequelSphere使用SQL函数UUID()来简单地实现这一点。将UUID作为每行的主键允许在任何时间在任何客户端上生成主键,而无需联系服务器,并且仍然保证ID是唯一的。这也使应用程序能够在“离线”模式下工作,在运行时不需要连接服务器。这还可以防止宕机的服务器导致所有客户端都崩溃。
第二:使用与服务器镜像的单个表集。
这更多是为了简单而非其他任何原因。这也是下面两个基本原则的要求。
第三:对于小数据集的向下同步,从服务器完全刷新客户端数据更可取。
尽可能地从服务器上进行完整的数据刷新。这是一个更简单的范例,并且会导致较少的内部数据完整性问题。主要缺点是数据传输大小。
第四:对于大数据集的向下同步,请执行“事务性”更新。
这里的方法会更加复杂。如果数据集过大,需要仅同步更改行,则必须按照“事务”进行同步。也就是说:按在服务器上执行它们的插入/更新/删除操作的顺序提供一个简单的脚本,在客户端执行相同操作。
最好在服务器上有一张记录要同步到设备的事务表。如果不可能,则可以经常在服务器上使用时间戳记录顺序,并让客户端请求自某个时间戳以来所有更改。缺点:您需要通过“逻辑”删除或在其自己的表中跟踪已删除的行来跟踪它们。即使如此,将复杂性隔离到服务器上比将其分散到所有客户端中更可取。
第五:对于向上同步,请使用“事务性”更新。
这就是SequelSphereDB的亮点:它会为您跟踪所有针对表执行的插入、更新和删除操作,并在同步时将它们提供给您。它甚至可以在浏览器重新启动时完成,因为它将信息持久化在localstorage/indexeddb中。它甚至可以适当地处理提交和回滚。客户端应用程序可以像往常一样与数据交互,而无需考虑记录更改,然后使用SequelSphereDB的“变更跟踪器”在同步时重放更改。
如果您没有使用 SequelSphere(您应该使用),则在客户端上保留单独的表以记录客户端执行的所有插入、更新和删除操作。每当客户端应用程序插入/更新/删除行时,在“事务”表中复制该行。在向上同步时,发送这些数据。在服务器上,按照相同的顺序执行相同的步骤以复制客户端上的数据。
还有一点很重要:始终在完全从服务器刷新客户端表之前执行向上同步。 :)
结论
我建议尽可能在许多地方采用简单而不是复杂的方法。在此过程中,使用UUID作为主键非常有帮助。使用某种“更改跟踪器”也非常有用。使用诸如SequelSphereDB之类的工具来跟踪更改最有帮助,但对于这种方法并非必需。
完全披露:我与SequelSphere公司有密切关系,但实施上述方法并不需要该产品。

谢谢John,你的见解很棒。我开始实现一个原型,并且得出了和你差不多的结论。但是你解释得更好 :) 不过,我不确定是否有必要在客户端记录每次记录的变化。难道记录的更改不是没有记忆力的吗?也就是说,您只需要知道记录的最后一个状态。我的做法是在客户端创建一个“未同步”表,并存储所有已更改但尚未提交到服务器的记录。一旦记录被提交并与服务器同步,它就会从“未同步”中删除。这样做有什么缺点吗? - Panagiotis Panagi
确实,在客户端创建ID是正确的方法。一个缺点是JavaScript无法生成真正全局唯一的ID,正如这里所指出的https://dev59.com/7HVD5IYBdhLWcg3wDXF3#105074。但当然,可以通过让服务器验证ID并立即返回任何更改的ID来克服这个问题。 - Panagiotis Panagi
1
关于更改不具有记忆性:这完全取决于数据。有时创建子行需要父行具有特定的值。或者换句话说:在一组复杂对象中维护引用完整性(RI)的最简单方法就是“重放”客户端上发生的插入、更新和删除操作。通常,这只需要很少的额外数据和工作量,并且带来了简单性的好处。确定更改的行比记录更改难得多。您的方法是拥有一个表格或更改,听起来不错。 - John Fowler
1
关于GUIDs/UUIDs:使用随机生成GUIDs的算法,客户端重复的概率是极小的。然而,在另一个项目中我采取的方法是让服务器为每个客户端预先生成1000个ID,并将它们发送给客户端在需要时使用。这消除了与服务器验证的需要,同时也保证了唯一性。但这也带来了生成许多未使用ID的代价。但只要这些ID没有意义,那就不应该成为问题。 - John Fowler

0
  • 不要使用本地存储。
    我不确定您的要求是什么,但在它到达“真相之源”数据库之前,它并不真实。
  • 如果您必须使用本地存储,可能是为了在断开连接的状态下工作,请在本地计算机上使用SQL Express,并将数据复制回“真相之源”数据库服务器。

这样,您只需要编写一个中间层来访问本地数据库,它会处理其他所有事情。您不必为主键使用GUID,SQL Server足够智能,可以为您处理这个问题,但这仍然不是一个坏主意。在我们的一个应用程序中,我们公开了一个GUID来标识数据库外部的实体,但在内部使用bigint。这是两全其美的最佳选择。

祝您好运。


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