动机:协作与所有权
如今我们在线协作的便利程度令人惊叹。我们用 Google Docs 协作撰写文档、表格和演示文稿;在 Figma 里一起做用户界面设计;用 Slack 与同事沟通;在 Trello 里跟踪任务;诸如此类。我们依赖这些以及许许多多其他在线服务,例如记笔记、规划项目或活动、保存联系人,还有一大堆商业用途。
与前几代软件相比,今天的云应用带来了巨大的好处:无缝协作,以及从任何设备访问数据。随着我们把越来越多的生活与工作交给这些云应用,它们对我们也越来越关键。我们在某个应用上投入的时间越多,其中的数据对我们就越有价值。
然而,在研究过程中,我们与许多创意工作者交流过,也由此了解到云应用的另一面。
当你投入了大量创造性精力与心血做出某样东西,你往往会对它产生深厚的情感依恋。如果你从事创造性工作,这种感觉大概不陌生。(我们说的「创造性工作」不仅指视觉艺术、音乐或诗歌,许多其他活动,比如讲解一个技术主题、实现一个精巧的算法、设计一个用户界面,或是琢磨如何带领团队达成某个目标,同样是创造性的努力。)
在完成这些创造性工作的过程中,你通常会产出文件和数据:文档、演示文稿、电子表格、代码、笔记、绘图等等。而你会想保留这些数据:供将来参考与获取灵感,放进作品集,或者仅仅因为引以为豪而存档。对这些数据感到拥有很重要,因为创造性表达是如此私人的事情。
遗憾的是,云应用在这方面存在问题。虽然它们让你能在任何地方访问数据,但所有数据访问都必须经过服务器,而你只能做服务器允许你做的事。从某种意义上说,你并不完全拥有这些数据,拥有它们的是云服务商。正如一张车尾
当数据存储在「别人的电脑」上时,那个第三方就对数据取得了某种程度的控制。云应用以服务的形式提供;服务不可用时,你就无法使用该软件,也无法再访问用它创建的数据。如果服务关停,即便你或许能导出数据,没有了服务器,你通常也无法继续运行自己的一份软件副本。于是,你只能听凭服务提供商的摆布。
在网页应用出现之前,我们用的是可称为「老式」的应用:运行在本地电脑上、读写本地磁盘文件的程序。今天我们仍在使用大量这类应用:文本编辑器和 IDE、Git 及其他版本控制系统,以及许多专业软件包,比如图形应用或 CAD 软件,都属于此类。
在老式应用中,数据以文件形式存放在你的本地磁盘上,所以你对这些数据拥有完全的自主权和所有权:你想做什么都可以,包括长期归档、做备份、用其他程序处理这些文件,或者在不再需要时删除它们。访问自己的文件不需要任何人的许可,因为它们是你的。你也不必依赖另一家公司运营的服务器。
总结一下:云给了我们协作,而老式应用给了我们所有权。难道我们不能两者兼得吗?
我们既想要云应用提供的便捷跨设备访问与实时协作,也想要「老式」软件所体现的对自己数据的个人所有权。
本地优先软件的七个理想
我们相信,数据所有权与实时协作并不相互冲突。完全可以创造出这样的软件:它拥有云应用的全部优点,同时又让你保留对自己所创建的数据、文档和文件的完整所有权。
我们把这类软件称为本地优先软件,因为它优先使用本地存储(电脑内置的磁盘)和本地网络(例如家里的 WiFi),而不是远程数据中心里的服务器。
在云应用中,服务器上的数据被视为主副本、权威副本;客户端若持有一份数据,那也只是从属于服务器的缓存。任何数据修改都必须发送到服务器,否则就「没发生过」。在本地优先应用中,我们把这两个角色对调:把你本地设备(笔记本、平板或手机)上的数据副本视为主副本。服务器仍然存在,但它们保存的是数据的次要副本,用于辅助多设备访问。正如我们将要看到的,这一视角的转变会带来深远的影响。
以下是我们希望本地优先软件努力达成的七个理想。
1. 没有转圈圈:你的工作触手可及
今天的许多软件用起来比前几代软件

全球各地 AWS 数据中心之间的服务器往返时延。数据来源:Peter Bailis、Aaron Davidson、Alan Fekete 等,「Highly Available Transactions: Virtues and
对于云应用,由于数据的主副本在服务器上,所有的数据修改以及许多数据查询都需要与服务器往返一次。取决于你住在哪里,服务器很可能位于另一个大洲,于是光速为软件能有多快设定了上限。
本地优先软件则不同:由于数据的主副本保存在本地设备上,用户永远不需要等待某个服务器请求完成。所有操作都可以通过读写本地磁盘上的文件来处理,与其他设备的数据同步则在后台静静进行。
虽然这本身并不能保证软件一定快,但我们预期本地优先软件有潜力对用户输入做出近乎即时的响应,永远不必让你对着转圈圈等待,让你的数据始终触手可及。
2. 你的工作不会被困在一台设备上
今天的用户依靠多台计算设备完成工作,现代应用必须支持这样的工作流。例如,用户可能在路上用手机记下想法,在平板上整理和思考这些想法,最后在笔记本电脑上把结果敲成一篇文档。
这意味着,本地优先应用虽然把数据保存在每台设备的本地存储中,但这些数据还必须在用户工作所用的所有设备之间同步。数据同步技术有很多种,我们会在后面的章节详细讨论。
大多数跨设备同步服务也会在服务器上保存一份数据副本,这为数据提供了方便的异地备份。只要每个文件同一时间只由一个人编辑,这些方案就运作得相当好。如果多个人同时编辑同一个文件,就可能产生冲突,我们会在协作一节中讨论。
3. 网络是可选的
个人移动设备会穿行于网络状况各异的环境:不可靠的咖啡馆 WiFi、飞机上、穿越隧道的火车里、电梯里或停车场。在发展中国家或乡村地区,互联网基础设施有时并不完善。出国旅行时,许多手机用户因漫游费用而关闭蜂窝数据。总之,对能离线使用的应用有大量需求,比如需要在野外写作的研究者或记者。
「老式」应用在没有网络连接时运行良好,而云应用通常离线就无法工作。多年来,离线
由于本地优先应用把数据的主副本存储在每台设备的本地文件系统中,用户可以随时读写这些数据,即使离线也不例外。之后在有网络连接时,数据再与其他设备同步。数据同步也不一定非要经过互联网:本地优先应用也可以用蓝牙或本地 WiFi 把数据同步到附近的设备。
此外,为了获得良好的离线支持,软件最好以本地安装的可执行程序形式运行在你的设备上,而不是浏览器中的一个标签页。对移动应用来说,整个应用在使用前先下载安装,早已是标准做法。
4. 与同事无缝协作
协作通常意味着多个人向同一份文档或文件贡献内容。然而在老式软件中,多个人同时处理同一个文件是有问题的:结果往往是冲突。对于源代码这类文本文件,解决冲突既繁琐又烦人;而对于电子表格或图形文档这类复杂文件格式,这项任务很快就变得极其困难甚至不可能。因此,协作者可能不得不事先约定谁来编辑某个文件,并且同一时间只允许一个人做修改。

Dropbox 上的「冲突

在 Evernote 中,如果一条笔记被并发修改,它会被移到一个「冲突变更」

在 Git 和其他版本控制系统中,多个人可能在不同的提交里修改同一个文件。合并这些改动常常导致合并
另一方面,Google Docs 这样的云应用极大地简化了协作:多个用户可以同时编辑一份文档,无需通过邮件来回传文件,也不必担心冲突。用户已经开始期待在各种各样的应用中都能有这种无缝的实时协作。
在本地优先应用中,我们的理想是支持不逊于甚至优于今天最好的云应用的实时协作。实现这一目标是落实本地优先软件的最大挑战之一,但我们相信这是可能的:在后面的章节中,我们会讨论在本地优先环境下实现实时协作的技术。
此外,我们预期本地优先应用能够支持多种协作工作流。除了多人实时编辑同一份文档之外,有时也需要一个人先试探性地提出修改,由另一个人审阅并有选择地采纳。Google Docs 用它的建议

在 Google Docs 中,协作者既可以直接编辑文档,也可以提出修改

GitHub 上的协作工作流建立在拉取
5. 长久的当下
数据所有权的一个重要方面,是你在很久以后的将来仍能继续访问这些数据。当你用本地优先软件完成了一些工作,这些工作应当能被无限期地访问下去,即使做出这款软件的公司已经不复存在。

泥板上的楔形文字,约公元前 3000 年。图片来自 Wikimedia
只要你有一份数据副本,并且有办法运行软件,「老式」应用就能永远用下去。即使软件作者破产,你仍可以继续运行最后发布的版本。即使操作系统和它所运行的电脑都已过时,你仍可以在虚拟机或模拟器里运行这款软件。随着存储介质在几十年间不断演进,你可以把文件复制到新的介质上,继续访问它们。
另一方面,云应用依赖于服务持续可用:服务不可用,你就无法使用软件,也无法再访问用它创建的数据。这意味着你是在赌软件的创造者会长期持续支持它,至少要和你在乎这些数据的时间一样长。
虽然 Google 短期内关闭 Google Docs 的危险看上去不大,但流行的
本地优先软件能带来更好的长久性,因为你的数据,以及读取和修改这些数据所需的软件,全都存储在你自己的电脑上。我们认为这不仅对你自己重要,对将来想要阅读我们今天所创建文档的历史学家也同样重要。没有数据的长久性,我们就有可能造成 Vint Cerf 所说的「数字
有些文件格式(例如纯文本、JPEG 和 PDF)已经无处不在,在未来几个世纪里大概都能被读取。美国国会图书馆也推荐 XML、JSON 或
6. 默认安全与隐私
云应用架构的一个问题是,它们把所有用户的所有数据都存放在一个集中式数据库里。这个庞大的数据集合对攻击者极具吸引力:一个
Google 拥有世界一流的安全团队,但令人遗憾的现实是,大多数公司并没有。而且,尽管 Google 擅长保护你的数据免受外部攻击,公司内部却可以随意以无数种方式使用你的数据,例如把你的数据喂给它的机器学习系统。
也许你觉得自己的数据不会引起任何攻击者的兴趣。然而,对许多职业来说,处理敏感数据是工作的重要部分。例如,医疗从业者处理敏感的患者数据,调查记者处理来自线人的机密信息,政府和外交代表进行敏感的谈判,等等。由于合规要求和保密义务,其中许多专业人士无法使用云应用。
而本地优先应用则在核心层面内置了更好的隐私与安全。你的本地设备只存储你自己的数据,避免了那个保存着所有人数据的集中式云数据库。本地优先应用可以使用端到端加密,这样任何存有你文件副本的服务器都只持有它们无法读取的加密数据。
7. 你保有最终的所有权与控制权
对于云应用,服务提供商有权限制用户的访问:例如 2017 年 10 月,多名 Google Docs 用户被锁在自己的文档
为了澄清此处「所有权」的含义:我们指的不是知识产权的法律意义。举例来说,一个文字处理器不应该关心正在编辑的文本的版权归谁。我们所说的所有权,指的是用户对数据的自主权、自治权和控制权。你应当能以任何方式复制和修改数据,写下任何想法,而没有任何公司来限制你被允许做什么。
在云应用中,你访问和修改数据的方式受限于服务提供商的 API、用户界面和服务条款。而在本地优先软件中,构成你数据的每一个字节都存储在你自己的设备上,所以你可以自由地以任意方式处理这些数据。
拥有数据也意味着责任:维护备份或其他防止数据丢失的预防措施,防范勒索软件,以及对文件存档的日常整理与管理。对于引言中提到的许多专业和创意用户,我们相信「多一些责任换取多一些所有权」这笔交易是值得的。想想一件重要的个人创作,比如一篇博士论文或一部电影的原始素材。为了确保数据安全并完全在自己掌控之下,你或许愿意为它们的存储和备份承担责任。
现有的数据存储与共享模式
我们相信,专业用户和创意用户值得拥有实现本地优先目标的软件,帮助他们无缝协作,同时保留对自己作品的完整所有权。如果我们能在他们用来完成最重要工作的软件中提供这些特质,就能帮助他们做得更好,并有可能给许多人的职业生涯带来显著的改变。
然而,即便本地优先软件的理想引起了你的共鸣,你可能仍会怀疑它们在实践中究竟有多可行。它们会不会只是乌托邦式的空想?
在本文余下的部分,我们将讨论在实践中实现本地优先软件意味着什么。我们考察了一系列现有技术,并逐项分析它们在多大程度上满足本地优先的理想。在下面的表格中,✓ 表示该技术满足这一理想,— 表示部分满足,✗ 表示不满足。
我们将看到,许多技术满足其中一部分目标,但没有一种能全部满足。最后,我们会考察一项来自计算机科学研究前沿的技术,它可能成为未来实现本地优先软件的一块基石。
应用架构如何影响用户体验
我们先从最终用户的视角审视软件,分析不同的软件架构在多大程度上满足本地优先软件的七个理想。在下一节中,我们再比较软件工程师用来构建应用的存储技术与 API。
文件与邮件附件
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| 文件 + 邮件附件 | ✓ | — | ✓ | ✗ | ✓ | — | ✓ |
从我们七个目标的角度来看,传统文件有许多可取的特性:可以离线查看和编辑,把完整的控制权交给用户,而且很容易备份和长期保存。依赖本地文件的软件也有潜力做到非常快。
然而,从多台设备访问文件就比较麻烦了。把文件在设备间传来传去,可以用各种技术:
- 通过电子邮件来回发送;
- 用 U 盘来回拷贝;
- 通过分布式文件系统,例如 NAS
服务器 、NFS 、FTP 或rsync ; - 使用云端文件存储服务,例如 Dropbox、Google Drive 或 OneDrive;
- 使用版本控制系统,例如 Git。
其中,邮件附件大概是最常见的共享机制,尤其是在非技术专家的用户之间。附件容易理解,也值得信赖。一旦你拿到了一份文档的副本,它不会自己发生变化:六个月后再打开那封邮件,附件仍以原样待在那里。与网页应用不同,打开一个附件不需要任何额外的登录流程。
邮件附件最薄弱的一环是协作。一般来说,同一时间只能有一个人修改文件,否则就需要艰难的手动合并。文件版本很快就会变得一团糟:一个带附件来回往复的邮件线程,常常催生出像 预算草案2(Jane的版本)最终版最终版3.xls 这样的文件名。
尽管如此,对于想要融入本地优先理念的应用来说,一个不错的起点是提供导出功能,生成广泛支持的文件格式(例如纯文本、PDF、PNG 或 JPEG),并允许通过邮件附件、Slack 或 WhatsApp 等方式分享。
网页应用:Google Docs、Trello、Figma、Pinterest 等
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| Google Docs | — | ✓ | — | ✓ | — | ✗ | — |
| Trello | — | ✓ | — | ✓ | — | ✗ | ✗ |
| ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
光谱的另一端是纯粹的网页应用:用户的本地软件(网页浏览器或移动应用)只是一个瘦客户端,数据存储位于服务器上。服务器通常使用一个大规模数据库,把数百万用户的数据全都混在一个巨大的集合里。
网页应用为实时协作树立了标准。作为用户,你可以确信在任何设备上打开一份文档时,看到的都是最新、最及时的版本。这对团队工作太有用了,以至于这类应用已经占据了主导地位。就连 Microsoft Office 这种传统上纯本地的软件也在向云服务转型,Office 365 自 2017 年起已超越本地安装版
随着远程办公与分布式
硬币的另一面是所有权与控制权的彻底丧失:服务器上的数据才算数,你客户端设备上的任何数据都无关紧要,那只是缓存。大多数网页应用对离线工作的支持很少或根本没有:网络哪怕只是打个嗝,你就会被锁在工作之外,话说到一半也不行。

Google Docs 一旦检测到离线,就会阻止编辑文档。
少数最优秀的网页应用会用 JavaScript 掩盖与服务器通信的

用户对 Google Docs 离线扩展的一条差评。
有些网页应用,例如 Milanote 和 Figma,提供可安装的桌面客户端,但那本质上是重新打包的网页浏览器。如果你在网络时断时续、厂商服务器故障,或者厂商被收购并关停之后,试图用这些客户端访问自己的工作,你会清楚地意识到:这些工作从来就不真正属于你。

Dropbox、Google Drive、Box、OneDrive 等
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| Dropbox | ✓ | — | — | ✗ | ✓ | — | ✓ |
由于这些工具使用本地文件系统,它们有许多吸引人的特性:访问本地文件很快,离线工作也没有问题(离线编辑的文件会在下次联网时同步)。如果同步服务关停,你的文件仍会完好无损地留在本地磁盘上,切换到另一家同步服务也很容易。如果电脑硬盘坏了,只需安装应用并等待同步完成,就能恢复你的工作。这为数据提供了良好的长久性与控制权。
然而,在移动平台(iOS 和 Android)上,Dropbox 及其同类采用了完全不同的模式。移动应用并不同步整个文件夹,而是作为瘦客户端,从服务器一次取一个文件,并且默认不能离线工作。虽然有一个「设为可离线

Dropbox 移动应用的用户花大量时间盯着转圈圈,这与 Dropbox 桌面产品那种触手可及的感觉形成了鲜明对比。
文件同步产品最薄弱的一环是缺乏实时协作:如果同一个文件在两台不同的设备上被编辑,结果就是一个需要手动合并的冲突,正如前文所讨论的。这些工具能同步任何格式的文件,这既是优势(与任何应用兼容),也是劣势(无法进行针对特定格式的合并)。
Git 与 GitHub
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| Git+GitHub | ✓ | — | ✓ | — | ✓ | — | ✓ |
像 GitHub 这样的仓库托管服务,使围绕 Git 仓库的协作和多设备访问数据成为可能,同时也提供了备份与归档的位置。目前对移动设备的支持还比较弱,不过 Working
我们认为 Git 模式指明了本地优先软件的未来方向。然而,就目前而言,Git 有两个主要弱点:
- Git 非常擅长异步协作,尤其是通过拉取
请求 :它把一组粗粒度的改动打包,允许在合并进共享主分支之前讨论和修订。但 Git 不具备实时、细粒度协作的能力,比如 Google Docs、Trello 和 Figma 等工具中那种自动的、即时的合并。 - Git 为代码及类似的按行组织的文本文件做了高度优化;其他文件格式被当作二进制块处理,无法有意义地编辑或合并。尽管 GitHub 努力展示和比较
图片 、散文 和 CAD文件 ,非文本文件格式在 Git 中仍然是二等公民。
有趣的是,大多数软件工程师一直不太愿意把自己的编辑器、IDE、运行环境和构建工具搬到云软件上。理论上,我们本以为这群老练的用户会比其他类型的用户更早拥抱新技术。但如果你问一位工程师为什么不用
构建应用的开发者基础设施
我们已经从本地优先理想的角度考察了一系列应用的用户体验,现在让我们切换到应用开发者的思维模式。如果你正在开发一款应用,想为用户提供部分或全部的本地优先体验,在数据存储与同步的基础设施上有哪些选择?
网页应用(瘦客户端)
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| 网页应用 | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
最纯粹形式的网页应用,通常是一个运行在服务器上的 Rails、Django、PHP 或 Node.js 程序,把数据存在 SQL 或 NoSQL 数据库里,通过 HTTPS 提供网页。所有数据都在服务器上,用户的浏览器只是一个瘦客户端。
这种架构有许多好处:零安装(访问一个网址即可),用户无需管理任何东西,因为所有数据都由部署应用的工程和运维专业人员集中存储和管理。用户可以从自己的所有设备访问应用,同事们登录同一个应用就能轻松协作。
另一方面,一个每次用户操作都要向服务器发请求的网页应用注定会很慢。某些情况下可以用客户端 JavaScript 掩盖往返
尽管人们为让浏览器更适应离线做了许多努力(
依赖第三方网页应用,在长久性、隐私和用户控制方面同样得分很低。如果网页应用是开源的,并且用户愿意自己托管服务器实例,这些特性可以得到改善。然而,我们认为对于绝大多数不想成为系统管理员的用户来说,自托管并不是一个可行的
总而言之,我们推测,由于平台本质上的瘦客户端属性,网页应用永远无法提供我们所寻求的全部本地优先特性。选择构建网页应用,就是选择了一条数据属于你和你的公司、而不属于你的用户的道路。
带本地存储的移动应用(胖客户端)
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| 胖客户端 | ✓ | — | ✓ | ✗ | — | ✗ | ✗ |
iOS 和 Android 应用是本地安装的软件,整个应用二进制文件在运行前就已下载安装。尽管如此,许多应用仍然像网页应用一样是瘦客户端,需要服务器才能运作(例如 Twitter、Yelp 或 Facebook)。没有可靠的互联网连接,这些应用给你的就是转圈圈、错误信息和意外的行为。
然而,还有另一类移动应用更符合本地优先的理想。这些应用首先把数据存储在本地设备上,使用
这些胖客户端应用的优势在于速度快、可离线工作,因为与服务器的同步在后台进行。服务器关停后,它们通常仍能继续运作。至于隐私和用户对数据的控制,则因具体应用而异。
如果数据可能在多台设备上或由多个协作用户修改,事情就变得更难了。移动应用的开发者通常是最终用户应用开发方面的专家,而不是分布式系统专家。我们见过多个应用开发团队自行编写临时拼凑的差异比较、合并与冲突解决算法,由此产生的数据同步方案往往不可靠且脆弱。下一节讨论的更专门的存储后端可以帮上忙。
后端即服务:Firebase、CloudKit、Realm
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| Firebase、CloudKit、Realm | — | ✓ | ✓ | — | ✗ | ✗ | ✗ |
对你这位开发者来说,Firebase 提供了很棒的体验:你可以在 Firebase 控制台里自由地查看、编辑和删除数据。但用户却没有与之相当的方式来访问、操作和管理自己的数据,因此用户几乎没有所有权和控制权。

Firebase 控制台:对开发者很友好,对最终用户则是禁区。
Apple 的

得益于对 CloudKit 的使用,Ulysses 只需一个复选框,就能在用户所有已连接的设备间同步工作。
同一路线上的另一个项目是
把设备上的数据视为主副本(或者至少不只是一次性缓存),并使用 Firebase 或 iCloud 等同步服务的移动应用,已经让我们朝本地优先软件走了相当长的一段路。
CouchDB
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| CouchDB | — | — | ✓ | ✗ | — | — | — |
在理念上,CouchDB 与本地优先原则高度契合,这一点尤其体现在 CouchDB
虽然 CouchDB/PouchDB 允许多台设备并发修改数据库,但这些修改会产生需要由应用代码显式解决的冲突。这类冲突解决代码很难写对,使得 CouchDB 对于像 Google Docs 那样协作粒度极细、每一次击键都可能是一次独立修改的应用并不实用。
在实践中,CouchDB 模式并未得到广泛
迈向更好的未来
如上所示,现有的应用开发数据层没有一个能完全满足本地优先的理想。因此,三年前,我们的实验室着手寻找一个能拿到七个绿色对勾的方案。
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| ??? | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
我们找到了一些看起来有望成为本地优先理想基础的技术。其中最值得关注的是一族叫作无冲突复制数据
作为基础技术的 CRDT
CRDT 于
每个应用都需要某种数据结构来存储其文档状态。例如,如果你的应用是文本编辑器,核心数据结构就是构成文档的字符数组。如果是电子表格,数据结构就是一个单元格矩阵,单元格里装着文本、数字或引用其他单元格的公式。如果是矢量绘图应用,数据结构就是一棵由文本对象、矩形、线条及其他形状等图形对象组成的树。
如果你在构建单用户应用,你会用模型对象、哈希表、列表、记录/结构体之类的东西在内存中维护这些数据结构。如果你在构建协作式多用户应用,你可以把这些数据结构换成 CRDT。

两台设备起初拥有相同的待办清单。在设备 1 上,用 .push() 方法向清单末尾追加了一个新条目。与此同时,设备 2 上把第一个条目标记为已完成。两台设备通信之后,CRDT 自动合并了状态,两处改动都得以生效。
上图展示了一个由采用 JSON 数据
如果状态在不同设备上被并发修改,CRDT 会合并这些修改。例如,如果用户在不同设备上并发地向待办清单添加新条目,合并后的状态会以一致的顺序包含所有新增条目。对不同对象的并发修改也很容易合并。CRDT 唯一无法自动解决的一类修改,是多个用户并发更新同一对象的同一属性;此时,CRDT 会记录下相互冲突的值,留给应用或用户去解决。
因此,CRDT 与 Git 这类版本控制系统有几分相似,只不过它操作的是比文本文件更丰富的数据类型。CRDT 可以通过任何通信渠道同步状态(例如经由服务器、通过点对点连接、本地设备间的蓝牙,甚至用 U 盘)。CRDT 记录的改动可以小到一次击键,从而实现 Google Docs 风格的实时协作。但你也可以攒下一大批改动,作为一个批次发送给协作者,更像 Git 里的拉取请求。由于这些数据结构是通用的,我们可以为 CRDT 的存储、通信和管理开发通用工具,省得在每一个应用里重新实现这些东西。
想要更技术性地了解 CRDT,我们推荐:
- Alexei Baboulevitch 的 Data Laced with
History - Martin Kleppmann 的 Convergence vs
Consensus (幻灯片 ) - Shapiro 等人的综合
综述 - Attiya 等人对协作文本编辑的形式化
规范 - Gomes 等人对 CRDT 的形式化
验证
Ink & Switch 开发了一个开源的 JavaScript CRDT 实现,叫作
然而,基于使用它们的经验,我们相信 CRDT 有潜力成为新一代软件的基础。正如分组交换是互联网和万维网的使能技术,电容式触摸屏是智能手机的使能技术,我们认为 CRDT 可能成为让用户完全拥有自己数据的协作软件的基础。
Ink & Switch 的原型
虽然学术研究在设计 CRDT 算法和验证其理论正确性方面取得了良好进展,但迄今为止这些技术在工业界的应用相对较少。而且,工业界对 CRDT 的使用大多集中在以服务器为中心的计算上,但我们相信这项技术在面向创意工作的客户端应用中有着巨大的潜力。
正是出于这一动机,我们的
这些实验探索了三个方面的问题:
- 技术可行性。CRDT 离可用于实际工作的软件还有多远?网络通信需要什么?软件安装又需要什么?
- 用户体验。本地优先软件用起来感觉如何?在没有权威中心服务器的情况下,我们能否获得像 Google Docs 那样无缝的实时协作体验?对于源代码以外的数据类型,能否获得类似 Git 的、对离线友好的异步协作体验?更一般地说,没有中心服务器时,用户界面会有什么不同?
- 开发者体验。对应用开发者来说,与 SQL 数据库、文件系统或 Core Data 等现有存储层相比,使用基于 CRDT 的数据层是什么感觉?为分布式系统写软件更难吗?我们需要模式(schema)和类型检查吗?开发者用什么来调试和检视应用的数据层?
我们用
看板

Trellis 用本地优先软件提供了类似 Trello 的体验。右侧的变更历史反映了文档中所有活跃用户所做的修改。
在这个项目中,我们尝试用
在用户体验方面,我们受 Git 和 Google Docs 的「查看新
观看演示
协作绘图

实时一起画画。顶部的 URL 提供了与其他用户快速分享该文档的方式。右侧的「版本」面板显示当前文档的所有分支。箭头按钮可在分支之间即时合并。
在这个项目中,我们尝试通过 Dat
用户体验方面的实验包括:用 URL 分享文档,受 Git 启发的可视化分支/合并功能,用红色高亮冲突像素的冲突解决机制,以及通过用户手绘头像实现的基本用户身份。
媒体画布
PushPin 是一个混合媒体画布工作区,类似于

PushPin 的画布混合了文本、图片、讨论线程和网页链接。用户通过工具栏中的在线状态头像看到彼此,并通过 URL 栏在自己的文档之间导航。
PushPin 探索了嵌套与相互关联的共享文档、CRDT 文档的多种渲染器、一个包含用于分享的「发件箱」模型的更高级身份系统,以及对选区高亮等临时数据的共享支持。
观看 PushPin 演示
发现
我们开发 Trellis、Pixelpusher 和 PushPin 这三个原型的目标,是评估本地优先软件与 CRDT 的技术可行性、用户体验和开发者体验。我们通过在开发团队(五名成员)内部定期使用这些原型、对开发过程进行批判性反思,以及对大约十名外部用户进行单独的可用性测试来检验它们。外部用户包括专业设计师、产品经理和软件工程师。我们没有遵循正式的评估方法,而是采取探索性的方式来发现原型的长处与短处。
在本节中,我们概述从构建和使用这些原型中学到的经验。虽然这些发现多少带有主观性,但我们相信它们仍包含有价值的洞见,因为在通往基于 CRDT 的、可投入生产的本地优先应用这条路上,我们比其他项目走得更远。
CRDT 技术是可行的。
从一开始,Automerge 的可靠性就让我们惊喜。团队里的应用开发者能够相对轻松地集成这个库,数据的自动合并几乎总是直接而无缝的。
离线工作的用户体验极佳。
断开网络、想工作多久就工作多久、然后重新连接并与同事合并改动,这一流程运作良好。当系统上的其他应用抛出错误(「离线!警告!」)并阻止用户工作时,本地优先的原型无论网络状态如何都能正常运作。与基于浏览器的系统不同,用户永远不必焦虑应用能否工作、需要时数据是否还在。这给了用户一种对自己的工具和工作的拥有感,正如我们所期望的那样。
与函数式响应式
React 的 FRP 模型与 CRDT 十分契合。基于 CRDT 的数据层意味着用户的文档既在接收本地用户的更新(例如他们在文本文档中打字),也在接收来自网络的更新(其他用户和其他设备对文档的修改)。
由于 FRP 模型能可靠地让应用的可见状态与共享文档的底层状态保持同步,开发者就从跟踪其他用户传来的改动并与当前视图协调这一繁琐工作中解放出来。此外,通过确保对底层状态的所有修改都经由单一函数(一个「
这一模型的结果是,我们所有的原型都以应用开发者很少的投入实现了实时协作和完整的离线能力。这是一个显著的好处,因为它让应用开发者能专注于自己的应用,而不是数据分发的难题。
冲突并不像我们担心的那样严重。
我们经常被问到自动合并的效果如何,许多人认为需要针对具体应用的冲突解决机制。然而我们发现,用户在与他人协作时遇到冲突的频率出奇地低,而通用的解决机制运作良好。原因如下:
- Automerge 在细粒度上跟踪改动,并考虑数据类型的语义。例如,如果两个用户并发地在数组的同一位置插入条目,Automerge 会把两个新条目按确定性的顺序排列,从而合并这些改动。相比之下,Git 这类文本版本控制系统会把这种情况视为需要手动解决的冲突。
- 用户对人类协作有直觉,会避免与协作者制造冲突。例如,当用户协作编辑一篇文章时,他们可能事先约定一段时间内谁负责哪一节,避免并发修改同一节。
当不同用户并发修改文档状态的不同部分时,Automerge 会毫无困难地干净合并这些改动。以看板应用为例,一个用户可以在某张卡片上发表评论,另一个用户可以把它移到另一列,合并结果会同时反映这两处改动。只有当用户并发修改同一对象的同一属性时才会产生冲突:例如两个用户并发地改变画布上同一个图片对象的位置。在这类情况下,解决方式往往是任意的,而且无论怎么解决都能令人满意。
Automerge 的数据结构自带一小组针对并发修改的默认解决策略。原则上,人们可能预期不同的应用需要不同的合并语义。然而,在我们开发的所有原型中,我们发现默认的合并语义就已足够,迄今尚未发现任何需要定制语义的情形。我们推测这在一般情况下也成立,并希望未来的研究能进一步检验这一假设。
文档历史的可视化很重要。
在分布式协作系统中,另一个用户随时可能向你传来任意数量的改动。与由服务器居中调解变更的中心化系统不同,本地优先应用需要自己找到解决这些问题的办法。没有合适的工具,就很难理解一份文档是如何变成现在这个样子的、存在哪些版本,或者各处贡献来自何方。
在 Trellis 项目中,我们尝试了一个「时间旅行」界面,允许用户回到过去查看合并后文档的早期状态,并在收到其他用户的改动时自动高亮最近变化的元素。能够以线性方式遍历一段可能十分复杂的合并文档历史,有助于提供上下文,并有可能成为理解协作的通用工具。
URL 是一种不错的分享机制。
我们尝试了多种与其他用户分享文档的机制,发现受万维网启发的 URL 模型对用户和开发者来说最合理。URL 可以复制粘贴,通过邮件或聊天等通信渠道分享。除了秘密 URL 之外,文档的访问权限仍是一个开放的研究问题。
点对点系统从来不是完全「在线」或「离线」的,数据在其中如何流动可能很难推理。
传统的中心化系统通常处于「正常」或「宕机」状态,每个客户端根据自己能否与服务器保持稳定的网络连接来判定。服务器决定了任何一条数据的真相。
在去中心化系统中,数据可能呈现万花筒般的复杂性。任何用户对于自己拥有哪些数据、选择分享哪些、接受哪些,都可能有不同的视角。例如,某个用户对文档的编辑可能留在飞机上的笔记本电脑里;飞机着陆、电脑重新联网后,这些改动才分发给其他用户。其他用户可以选择把这些改动全部、部分或完全不接受到自己的文档版本中。
文档的不同版本可能引起困惑。就像 Git 仓库一样,某个用户在「master」分支上看到的内容,取决于他上一次与其他用户通信的时间。新到达的改动可能出人意料地修改你正在处理的文档部分,但手动合并每个用户的每一处改动又太繁琐。去中心化的文档让用户掌控自己的数据,但这在实际的用户界面层面意味着什么,还需要进一步研究。
CRDT 会积累大量的变更历史,带来性能问题。
我们团队把 PushPin 用于冲刺规划等「真实」文档。性能和内存/磁盘占用很快成了问题,因为 CRDT 存储全部历史,包括逐字符的文本编辑。这些历史不断堆积,却不能轻易截断,因为无法知道什么时候会有人在离开六个月后重新连接到你的共享文档,需要从那个时间点开始合并改动。
我们仍在持续优化 Automerge,但这是一个正在进行的主要工作领域。
网络通信仍是一个未解决的问题。
CRDT 算法只负责数据的合并,对于不同用户的编辑如何到达同一台物理计算机却只字未提。
在这些实验中,我们尝试了通过
CRDT 并不要求点对点的网络层;用服务器来通信对 CRDT 来说也完全可以。然而,为了充分实现本地优先软件的长久性目标,我们希望应用能比厂商管理的任何后端服务活得更久,因此去中心化方案是合乎逻辑的最终目标。
在原型中使用 P2P 技术的结果好坏参半。一方面,这些技术离生产可用还差得远:尤其是 NAT
云服务器在发现、备份和突发计算方面仍有其用武之地。
像 PushPin 这样的实时协作原型,让用户无需中间服务器就能与其他用户分享文档。这对隐私和所有权极好,但也可能出现这样的情况:一个用户分享了文档,然后在另一个用户连上之前就合上了笔记本盖子。如果用户不在同一时间在线,他们就无法彼此连接。
因此,服务器在本地优先的世界中仍有一席之地,不是作为中央权威,而是作为「云端对等节点」(cloud peer),在不处于关键路径上的前提下为客户端应用提供支持。例如,一个存储文档副本、并在其他对等节点上线时把它转发过去的云端对等节点,就能解决上面那个合上笔记本的问题。
类似地,云端对等节点还可以是:
- 归档/备份的位置(尤其是对手机或其他存储有限的设备);
- 通往传统服务器 API 的桥梁(例如天气预报或股票行情);
- 突发计算资源的提供者(例如用强大的 GPU 渲染视频)。
传统系统与本地优先系统的关键区别,不在于没有服务器,而在于服务器职责的改变:它们处于辅助地位,而不是真相之源。
你能做些什么
这些实验表明,本地优先软件是可能的。协作与所有权并不相互冲突,我们可以两全其美,用户可以从中受益。
然而,底层技术仍在发展之中。它们适合开发原型,我们也希望它们在未来几年里不断演进并趋于稳定,但现实地说,今天在生产环境中用 Automerge 这样的实验性项目替换 Firebase 这样经过验证的产品,还不是明智之举。
如果你和我们一样相信本地优先的未来,那么你(以及我们所有技术领域的人)能做些什么来推动它到来?这里有一些建议。
致分布式系统与编程语言研究者
本地优先软件从近年来的分布式系统研究中获益良多,包括 CRDT 和点对点技术。当前的研究社区在提升 CRDT 的性能与能力方面进展出色,我们热切期待这些工作的更多成果。尽管如此,仍有一些有趣的机会值得进一步探索。
大多数 CRDT 研究都基于这样一个模型:所有协作者立即把自己的编辑应用到文档的单一版本上。然而,实际的本地优先应用需要更大的灵活性:用户必须能自由地拒绝另一位协作者的编辑,或者在一个不与他人共享的文档版本上做私人修改。用户可能想试探性地应用某些改动,或者重新整理自己的变更历史。这些概念在分布式源代码管理领域早已为人熟知,叫作「分支」「派生」「变基」等等。而对于多个文档版本和分支并存的情形下,协作的算法与编程模型是什么,迄今几乎没有研究。
我们还看到围绕类型、模式迁移和兼容性的其他有趣问题。不同的协作者可能使用不同版本的应用,功能也可能不同。由于没有中央数据库服务器,就没有权威的「当前」数据模式。我们如何编写软件,使得不同版本的应用即便在数据格式演进的过程中也能安全地互操作?这个问题在基于云的 API 设计中有类似的对应,但本地优先的环境带来了额外的挑战。
致人机交互(HCI)研究者
对于中心化系统,当今领域里有大量应用向用户显示与服务器的「同步」状态的例子。去中心化系统则有一大堆有趣的新机会,等待人们去探索其中的用户界面挑战。
我们希望研究者思考:在任何其他用户都可能持有不同数据副本的系统中,如何传达在线与离线状态,或者可用与不可用状态。当每个人都是对等节点时,我们该如何理解连通性?当我们无需接入更广阔的互联网就能直接与其他节点协作时,「在线」意味着什么?

当每份文档仅凭日常操作就可能形成复杂的版本历史时,一个尖锐的问题出现了:我们该如何把这些版本历史传达给用户?在没有中心真相来源的情况下,用户该如何理解版本、分享和接受改动,并理解自己的文档是如何变成某个样子的?今天的变更管理有两种主流模型:源代码式的差异与补丁模型,以及 Google Docs 式的建议与评论模型。这就是我们能做到的最好的吗?如何把这些想法推广到非文本的数据格式?我们迫切想看看还能发现什么。
中心化系统严重依赖访问控制和权限,但同样的概念并不能直接套用到本地优先的环境中。例如,任何持有某份数据副本的用户都无法被阻止在本地修改它;然而,其他用户可以选择是否订阅这些改动。用户该如何理解分享、权限和反馈?如果我们无法从别人的电脑上删除文档,那么「停止与某人分享」意味着什么?
我们相信,中心化的假设已深深植根于今天的用户体验之中,而我们才刚刚开始发现改变这一假设的后果。我们希望这些开放问题能激发研究者去探索这个我们认为尚未开发的领域。
致从业者
如果你是软件工程师、设计师、产品经理或独立应用开发者,正在开发今天就要投入生产的软件,你能帮上什么忙?我们建议朝着本地优先的未来迈出渐进的步伐。先给你的应用打分:
| 1. 快速 | 2. 多设备 | 3. 离线 | 4. 协作 | 5. 长久 | 6. 隐私 | 7. 用户控制 | |
|---|---|---|---|---|---|---|---|
| 你的应用 |
然后,针对每个方面的一些改进策略:
- 快速。激进的缓存和提前下载资源,可以避免用户在打开你的应用或先前打开过的文档时看到转圈圈。默认信任本地缓存,而不是让用户等待网络获取。
- 多设备。Firebase 和 iCloud 这样的同步基础设施让多设备支持相对省心,尽管它们确实带来了长久性和隐私方面的顾虑。Realm Object
Server 这样的自托管基础设施提供了另一种取舍。 - 离线。在网页领域,渐进式网页
应用 提供了 Service Worker 和应用清单等有帮助的功能。在移动领域,要留意WebKit 框架及其他依赖网络的组件。关掉 WiFi 来测试你的应用,或者使用流量整形工具,例如 Chrome 开发者工具的网络状况模拟器 或 iOS 的网络链路调节器 。 - 协作。除 CRDT 之外,更成熟的实时协作技术是操作
变换 (Operational Transformation,OT),例如ShareDB 中的实现。 - 长久。确保你的软件能轻松导出为 JSON 或 PDF 这类扁平的标准格式。例如:像 Google
Takeout 那样的批量导出;像GoodNotes 那样持续备份为稳定的文件格式;以及像Trello 那样以 JSON 下载文档。 - 隐私。云应用在根本上是不私密的,公司员工和政府随时可以窥视用户数据。但对于移动或桌面应用,请尽量向用户说清楚:数据什么时候只存储在他们的设备上,什么时候会被传输到后端。
- 用户控制。用户能否在你的应用内轻松备份、复制或删除部分或全部文档?这通常意味着重新实现所有基本的文件系统操作,就像 Google Docs 通过 Google Drive 所做的那样。
致创业者
如果你是一位有意构建开发者基础设施的创业者,以上所有内容都指向一个有趣的市场机会:「面向 CRDT 的 Firebase」。
这样一家创业公司需要提供出色的开发者体验,以及一个本地持久化库(类似 SQLite 或 Realm 的东西)。它需要支持移动平台(iOS、Android)、原生桌面(Windows、Mac、Linux)和网页技术(Electron、渐进式网页应用)。
用户控制、隐私、多设备支持和协作都将内置其中。应用开发者可以专注于构建自己的应用,并且知道最省事的实现路径同时也能让他们在本地优先记分卡上拿到满分。作为检验你是否成功的试金石,我们建议:即使所有服务器都关停,你所有客户的应用是否仍能永久运行下去?
我们相信,随着 CRDT 走向成熟,「面向 CRDT 的 Firebase」将是一个巨大的机会。如果你正在做这件事,我们很想听到你的消息。
结论
计算机是人类有史以来创造的最重要的创作工具之一。软件已成为我们完成工作的管道,也是这些工作所栖身的仓库。
为了追求更好的工具,我们把许多应用搬到了云端。云软件在许多方面优于「老式」软件:它提供可协作、始终最新、在世界任何地方都能访问的应用。我们不再操心自己运行的是哪个软件版本,或者某个文件存在哪台机器上。
然而,在云端,数据的所有权归属于服务器而非用户,于是我们成了自己数据的借用者。云应用中创建的文档,注定会在这些服务的创造者停止维护时消失。云服务是无法长期保存的。没有哪个「时光机」(Wayback Machine)能恢复一个已经下线的网页应用。互联网档案馆也保存不了你的 Google Docs。
在本文中,我们探索了未来软件的一条新路。我们已经表明,用户完全可以在保留对数据的所有权与控制权的同时,也享受我们与云联系在一起的那些功能:无缝协作,随处访问。两全其美是可能的。
但要在实践中实现本地优先的方法,还需要更多工作。应用开发者可以迈出渐进的步伐,例如改进离线支持、更好地利用设备上的存储。研究者可以继续改进本地优先软件的算法、编程模型和用户界面。创业者可以把 CRDT 和点对点网络等基础技术打磨成成熟的产品,为下一代应用提供动力。
今天,创建一个由服务器掌握所有数据的网页应用轻而易举。但构建尊重用户所有权与自主权的协作软件却太难了。要扭转这一局面,我们需要改进开发本地优先软件的工具。我们希望你能加入我们。
欢迎你的想法、问题或批评: