用 AT Protocol 基础设施构建更抗故障的 Local-First 应用

2026-07-13 22 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

Local-first 应用通常强调离线可用、数据归用户所有,以及网络恢复后自动同步。Jake Lazaroff 的分享把这个思路与 AT Protocol 连接起来:用户数据保存在个人数据服务器(PDS)中,应用则复用协议提供的同步与更新基础设施。这样一来,AT Protocol 不再只服务于社交网络,也可以成为协作文档、任务工具和其他分布式应用的底座。

从“应用拥有数据”转向“应用读取用户数据”

传统 SaaS 往往把数据库、身份、同步接口和业务逻辑绑定在一个后端中。客户端一旦离线,或者服务停止运营,用户对数据的访问能力就会明显下降。

AT Protocol 支持的方向有所不同:用户在 PDS 中维护自己的数据,应用围绕这些记录提供界面和业务能力。共享基础设施负责传播更新,应用不必为每一种客户端重新设计一套专有同步协议。

这种架构可以拆成三层:

  • 本地数据层:客户端保留可直接查询和修改的数据副本,断网时仍能工作。
  • 用户数据层:PDS 保存用户发布的协议记录,是跨设备同步和数据迁移的重要边界。
  • 派生服务层:索引器、搜索服务或 AppView 消费更新,生成适合查询的视图,但不应成为用户原始数据的唯一存放处。

关键变化不是简单地把数据库搬到另一台服务器,而是重新划分所有权:应用可以失效或被替换,但用户记录仍应留在用户控制的数据边界内。

韧性来自哪些地方

本地副本让“能否打开应用”不再完全取决于网络。用户的写操作可以先落盘,再进入待同步队列;连接恢复后,客户端将增量推送到 PDS。即使远端暂时不可用,编辑动作也不必丢失。

共享协议基础设施还降低了对应用专用后端的依赖。开发团队仍可能需要索引、搜索、通知或权限服务,但这些服务可以被视为可重建的派生组件,而不是数据唯一真相来源。

这并不意味着冲突会自动消失。两台设备可能同时修改同一条记录,协作工具还可能出现乱序更新。应用必须明确选择冲突策略,例如:

  • 对简单状态采用版本号或 compare-and-swap。
  • 对独立字段执行字段级合并。
  • 对富文本协作引入 CRDT 等成熟算法。
  • 对无法安全自动合并的内容保留两个版本,让用户裁决。

可以这样实践:先跑通本地写入与延迟同步

下面是一个可直接运行的最小实验。它使用 SQLite 保存本地数据和 outbox,并以共享目录模拟远端 PDS。这个目录适配器只是教学替身,不是 AT Protocol 的真实 API;接入真实 PDS 时,应将 push_to_pds 替换为对应的记录写入客户端,同时保留本地事务和重试边界。

将代码保存为 local_first_demo.py,需要 Python 3.10 或更高版本:

import argparse
import json
import sqlite3
import time
from pathlib import Path

DB = Path("local.db")
REMOTE = Path("mock-pds")


def connect():
    db = sqlite3.connect(DB)
    db.execute("PRAGMA journal_mode=WAL")
    db.executescript("""
        CREATE TABLE IF NOT EXISTS notes (
            id TEXT PRIMARY KEY,
            text TEXT NOT NULL,
            updated_at INTEGER NOT NULL
        );
        CREATE TABLE IF NOT EXISTS outbox (
            id TEXT PRIMARY KEY,
            payload TEXT NOT NULL
        );
    """)
    return db


def edit(note_id, text):
    now = time.time_ns()
    payload = {"id": note_id, "text": text, "updated_at": now}
    with connect() as db:
        db.execute(
            "INSERT OR REPLACE INTO notes VALUES (?, ?, ?)",
            (note_id, text, now),
        )
        db.execute(
            "INSERT OR REPLACE INTO outbox VALUES (?, ?)",
            (note_id, json.dumps(payload, ensure_ascii=False)),
        )
    print(f"saved locally: {note_id}")


def push_to_pds(payload):
    REMOTE.mkdir(exist_ok=True)
    target = REMOTE / f"{payload['id']}.json"
    target.write_text(
        json.dumps(payload, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def sync():
    with connect() as db:
        pending = db.execute("SELECT id, payload FROM outbox").fetchall()
        for note_id, raw in pending:
            try:
                push_to_pds(json.loads(raw))
            except OSError as exc:
                print(f"sync deferred for {note_id}: {exc}")
                continue
            db.execute("DELETE FROM outbox WHERE id = ?", (note_id,))
            print(f"synced: {note_id}")


def list_notes():
    with connect() as db:
        for row in db.execute(
            "SELECT id, text, updated_at FROM notes ORDER BY updated_at DESC"
        ):
            print(row)


parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
edit_parser = sub.add_parser("edit")
edit_parser.add_argument("id")
edit_parser.add_argument("text")
sub.add_parser("sync")
sub.add_parser("list")
args = parser.parse_args()

if args.command == "edit":
    edit(args.id, args.text)
elif args.command == "sync":
    sync()
else:
    list_notes()

运行以下命令即可观察“本地先提交、随后同步”的过程:

python local_first_demo.py edit note-1 "离线时写下的内容"
python local_first_demo.py list
python local_first_demo.py sync
cat mock-pds/note-1.json

这个示例故意保持简单,只演示两个关键约束:本地修改和 outbox 写入处于同一个 SQLite 事务中;只有远端写入成功后,待同步记录才会删除。生产实现还需要幂等键、指数退避、认证、远端版本检查和冲突处理。

接入 AT Protocol 时要补齐的边界

把实验改造成真实应用时,可以定义自己的记录类型,并通过词典描述记录结构和操作约定。记录应该保持小而独立,避免每次编辑都重写一个巨大的项目文档。对于多人实时编辑,可以把快照与操作日志分开:PDS 保存可迁移的用户记录,实时协作层处理低延迟操作,再把稳定结果写回用户数据层。

还要警惕几个工程风险:

  • 协议可用不等于应用可用:搜索索引或派生视图宕机时,客户端仍需提供基本的本地浏览和编辑能力。
  • 数据归用户不等于自动私密:敏感协作内容需要明确的加密、密钥分发和访问撤销方案。
  • 同步成功不等于语义正确:最后写入者获胜容易实现,却可能静默覆盖重要编辑。
  • 共享基础设施不等于零后端:通知、反滥用、索引和实时协作仍可能需要独立服务。

采用前的检查清单

适合优先尝试 AT Protocol local-first 架构的场景,通常具有用户生成记录、跨设备访问、离线编辑和可迁移需求。落地前至少确认:本地数据库能否独立启动,远端不可用时写操作是否持久化,重试是否幂等,冲突是否可见,以及派生索引能否从 PDS 数据重新构建。

真正的韧性并不来自“没有服务器”,而是来自服务器故障时应用仍可工作、派生服务丢失后仍可重建、应用退出市场后用户仍能带走数据。AT Protocol 提供了可复用的分布式基础设施,但本地事务、冲突语义、隐私与恢复流程仍然是应用开发者必须完成的工程工作。


相关推荐