跳转至

架构决策记录(ADR) 0001:持久化适配器边界

状态:已接受

日期:2026-04-30

背景

WorldForge 目前将世界以经验证的本地 JSON 文件形式持久化。该存储有意设计为单写者,适用于测试、示例、本地 harness 工作流和可检出的问题复现证据。运行服务、批处理工作进程或机器人实验室的宿主应用程序可能需要多写者持久性、锁定、备份/恢复、保留策略和模式迁移。

直接向核心添加数据库会模糊 WorldForge 框架契约与宿主方部署职责之间的界限。持久化存储还需要 WorldForge 无法从一次库调用中推断的运营决策:事务隔离、租约所有权、备份节奏、保留窗口、迁移发布和故障恢复。

决策

WorldForge 保留本地 JSON 作为默认持久化行为。持久化的多写者存储在被接受进入核心、可选扩展包或参考宿主之前,必须通过显式的 WorldPersistenceAdapter 边界进入。

适配器边界为:

WorldPersistenceAdapter
  save(world) -> None
  load(world_id) -> World
  list() -> list[WorldSummary]
  delete(world_id) -> None
  export(world_id) -> dict
  import(payload, *, new_id: str | None = None) -> World
  health() -> PersistenceHealth

每个实现都必须保持当前本地 JSON 的不变式:

  • 在访问存储前验证 world ID;
  • 在写入前和读取后验证序列化的 world 载荷;
  • 对格式错误的状态、缺失的 world、失败的写入和不安全的标识符,以响亮的失败作为回应;
  • 将提供方密钥、签名 URL、宿主路径和私有载荷排除在导出的问题证据之外;
  • 为运营者发射足够的类型化错误上下文,而无需依赖数据库特定的异常。

第一个持久化实现(若日后被接受)应为可选适配器或参考宿主集成。它不得向基础包添加数据库依赖。

必需的适配器设计

未来的实现 PR 必须包含:

  • 锁定: 定义单写者、乐观并发、咨询锁、租约所有权和过期锁恢复。
  • 迁移: 定义模式版本存储、正向迁移顺序、回滚策略,以及旧版 WorldForge 客户端的失败方式。
  • 备份与恢复: 记录哪些载荷足以恢复 world,如何检查完整性,以及恢复演练的流程。
  • 保留期: 将 world 状态保留与运行工作区证据保留分开,并记录删除保证。
  • 模式版本控制: 将适配器模式版本与 WorldForge 包版本分开存储,并在读写前进行验证。
  • 故障恢复: 明确可重试与终端性故障、部分写入清理以及面向运营者的诊断。

已拒绝的替代方案

用 SQLite 替换本地 JSON

SQLite 对本地工作流很有吸引力,但替换 JSON 会改变默认持久化接口,为简单示例增加迁移复杂性,并且仍无法解决服务宿主的分布式多写者协调问题。

在当前存储周围添加锁文件

锁文件会让本地 JSON 看起来比实际上更安全。它们不能定义备份、迁移、保留或跨宿主恢复,并且其行为因文件系统而异。

添加通用数据库 URL 设置

仅一个连接字符串并不能定义存储契约。没有适配器设计,WorldForge 将为未知的锁定、事务、迁移、备份和恢复语义负责。

将持久化完全移出 WorldForge

WorldForge 仍然需要本地 JSON 用于确定性测试、示例、harness 工作流和问题复现。移除它会降低检出体验,并将基本状态验证推入每个宿主。

后果

  • 当前的本地 JSON 行为保持权威性,不做更改。
  • 宿主应用程序仍然负责生产数据库、备份、保留和多写者协调。
  • WorldForge 仍然可以在不增加基础依赖的情况下,为未来的持久化适配器提供文档。
  • 任何未来的持久化适配器在实现前都有明确的边界和验收标准。

验证

默认持久化契约仍由现有的本地 JSON 和 CLI 测试覆盖:

uv run pytest tests/test_persistence*.py tests/test_cli_worlds.py

该架构决策记录(ADR)及其链接文档通过以下方式验证:

uv run pytest tests/test_docs_site.py
uv run mkdocs build --strict