
最近,我参加了 Local-First Conf,听了 Martin Kleppmann 的演讲,其中有一张幻灯片特别引起了我的注意:

这里是关键点:

但在深入讨论之前,让我们先了解一些背景。
什么是 "本地优先"?
如果你想了解更详细的定义,可以去 Ink & Switch,他们是第一个提出这个概念的地方。或者,去听听 Peter van Hardenberg 在 LocalFirst.fm 上的讲解。
但这里我给出一个简短版本:
本地优先 是一种软件设计理念,旨在优先将数据保存在本地设备上。虽然它偶尔会与互联网进行同步、获取数据或进行备份等操作,但其核心目的是尽量减少对网络的依赖。
如果软件根本不需要上网,那它就是纯粹的本地软件。
如果它无法在没有网络连接的情况下使用已经存在的数据,那它就是普通的云端软件。大家应该都知道这种类型的软件——比如,“对不起,Dave,我刚下载的歌曲因为网络断了 1 秒钟就无法播放了……”
而 本地优先 就处于这个“中间地带”——它结合了本地数据存储和偶尔的在线同步功能。我们喜欢它的原因是,它更有利于终端用户,也就是你我,而不是那些大型企业。
本地优先面临的挑战
本地优先软件的目标是将控制权归还给用户,让用户拥有自己的数据(因为数据保存在用户设备上)。这一点做得很好。
但是,本地优先软件依然需要依赖在线组件。举个例子,即便是个人使用的本地优先软件,仍然需要在多个设备之间进行同步。同步需要服务器,而服务器的存在是一种潜在的风险。
问题就出现在这里:开发了本地优先软件的公司如果倒闭了,虽然用户仍然拥有软件,但同步功能可能就会停止。这种情况常常发生——很多公司都不稳定。

那我们该怎么办呢?
解决方案:利用 Dropbox
解决方案是使用一种通用且可能会长时间存在的工具。我们需要的是一个普遍可用、人人都能访问、且有多个实现版本的同步服务作为后端支持。
最常见的云同步工具是什么?就是 Dropbox!当然,这里不一定是指 Dropbox,其他类似的云同步工具也可以,比如 iCloud Drive、OneDrive、Google Drive、Syncthing 等。
它们的优势在于,许多人已经在使用它们。如果微软或苹果公司倒闭了,人们仍然可以切换到其他服务。而且文件同步本身已经是一个商品化的服务。
不过,文件同步也有它的局限性,它是一个“傻”协议。你无法针对同步事件、更新通知或冲突解决等进行更多的操作。它并没有太多的 API,基本上就是把文件保存下来然后同步到云端。一旦发生冲突,最好的情况是生成两个文件,最糟糕的情况可能只有一个。
这种简化的设计有其优点和缺点。优点是,如果你能适应这种方式,它几乎可以在任何地方都工作,这是 Martin 演讲中提到的 互操作性。
缺点是:你能做的事情有限,而且可能并不是最优的方式。但这也许足够了?
版本 1:超级简单的方式
假设我们直接把状态保存为一个文件,然后让 Dropbox 进行同步(我这里使用 Syncthing,但其实这个思路是一样的)。这种方法非常简单:

保存状态文件

用 Dropbox 同步
但是,如果你在两台机器上同时修改了状态会发生什么呢?答案是:你会得到一个冲突文件。
通常,这会是个问题。但如果你使用 CRDT(无冲突的可合并数据类型)呢?
CRDT 有一个很大的优点:它们总是能被合并。虽然合并结果可能不是完美的,并且并非所有数据都能转化为 CRDT 类型,但如果你能将数据表示为 CRDT,就可以确保所有的合并都是无冲突的。
通过 CRDT,我们可以解决冲突,通过打开两个文件并合并状态来避免问题。简单吧?
即使这样,利用 Dropbox 作为同步层,仍然是可行的!但仍然有一些缺点:
冲突的文件名称在不同的服务商之间可能不一致
有些服务商甚至可能不处理冲突
CRDT 需要基于状态来实现
版本 2:每个客户端一个文件
为了解决冲突,我们可以采取一种方法:始终在本地编辑文件。因此,我们给每个客户端一个独立的文件,确保每个客户端只会修改自己文件中的数据。

这样一来,只要同步的文件不被修改,Dropbox 就不会报告任何冲突。每个客户端会监控并合并来自其他客户端的文件变更,而数据冲突将由 CRDT 自动解决。
版本 3:基于操作的方式
假如你的 CRDT 是基于操作的,意味着你传输的是操作本身而不是整个状态。这样,每次客户端的操作就只会记录到一个追加式的文件中。每个客户端只会修改自己本地的文件,因此 Dropbox 层面就不会有任何冲突:

操作被保存在一个文件中
每次同步时,只会同步新增的操作
然而,这样的操作日志会越来越长,而 Dropbox 并不总是可靠地高效同步文件中的增量数据。所以我们需要将操作分割成小块,减少 Dropbox 同步的负担,也让我们自己更容易处理这些增量:

把操作分成多个小块
记录操作的当前位置,确保只同步尚未应用的操作
理论上,这种方式也能支持 操作变换,比如在多人同时编辑时应用合并。
Demo 演示
我们可以通过一个简单的 GitHub 项目来演示这个概念:crdt-filesync。
这个 demo 使用了 Automerge 来合并文本编辑操作,因此它不仅仅是简单的文本差异合并,而是使用了真正的 CRDT。
结论
如果你想开发一款本地优先的软件,用户拥有完全的控制权和数据所有权,数据同步就是必须要解决的问题。
虽然 Dropbox 和其他文件同步服务非常基础,但它们已经足够支持一种简单且可行的同步方式。
当然,这种方式的实时性可能没有定制化解决方案那么高效,但对于普通的同步需求来说,它已经足够好。想象一下,如果 Obsidian Sync 只是“把文件放进文件夹里”,然后自动提供无冲突的同步,且完全免费?只需要你提供自己的云存储?
对很多用户来说,这听起来已经很不错了!

