Submission
平台 SDK 的登记、版本的状态、提交策略、发送与确认
程序实例通过所在运行环境的 SDK 把版本交给后端。Observer 的业务代码负责形成事实、维护身份和选择提交策略;SDK 负责本地保存、接好版本链、发送、确认和恢复。这样分工的理由见 ADR · 提交与 SDK。
分工
| Observer 的业务代码 | SDK |
|---|---|
| 定义 EntitySchema,识别对象,形成观测内容和 Observation | 登记程序实例和所需的 EntitySchema |
| 决定粒度、身份和历史保留 | 本地保存草稿和待发送的版本,接好版本链 |
| 选择提交策略,明确通知更新和结束 | 按依赖顺序发送,处理确认、重试和恢复 |
| 处理被拒绝的版本 | 把被拒绝的版本和原因交给业务代码 |
应用是否切换、事实是否仍在持续、被拒绝的版本怎样改写,都由业务代码判断。后端按 Schema 版本和 Core 的规则校验并保存。
接入
连接后端
业务代码提供后端地址。SDK 用 POST /versions 提交,按 API 组织请求、解析响应和错误;网络库和请求方式由各平台的 SDK 决定。
登记
启动时,业务代码把程序实例和所需的 EntitySchema 交给 SDK。SDK 先提交它们,再发送其他版本;网络不可用时,登记内容保存在本地,恢复后最先提交。程序实例的 EntityId 和 SchemaId 在重启后保持不变,SchemaId 是写在业务代码中的常量。程序实例的信息变化时,SDK 形成它的新版本,见 OBSERVER-4。
SDK 用 GET /entities/{entityId}/versions 读取 EntitySchema 的版本链。链中已有与业务代码给出的内容相同的 Schema 版本时,直接引用它;没有时,在当前版本之后形成新版本。内容是否相同按 API · 内容相同 判断。
例如两台设备上运行着新旧两个版本的前台状态 Observer,各自引用自己那一版 Schema,Schema 不会在两台设备之间来回改写。业务版本可以引用同一个 EntitySchema 中较旧的 Schema 版本,见 ENTITY-3。
版本的状态
前台状态 Observer 在 10:00 确认 VS Code 在前台,形成一份 Observation;10:05 再次确认,把结束边界延长到 10:05。这时这份 Observation 还没有发送,延长直接改写它在本地的内容,后端只会收到一个版本。版本一旦被别的版本引用或进入发送,就不再改写,之后的变化形成新版本。
| 状态 | 含义 | SDK 怎样处理 |
|---|---|---|
| 草稿 | 已本地保存,尚未发送,也没有被其他版本引用 | 可以合并或丢弃 |
| 已冻结 | 已被其他版本引用,或已进入发送 | 按原样发送和重试 |
| 已保存 | 后端已确认保存 | 移出发送队列,保留它的 VersionId 供之后引用 |
| 被拒绝 | 后端拒绝了它,并给出 rule | 交给业务代码,见被拒绝的版本 |
- 新版本的
previousVersionId指向这个实体在本地最新的版本,这个版本可以尚未保存。所以版本链在本地就已接好。 - 合并是改写草稿的内容,草稿的 VersionId 和
previousVersionId不变。 - 业务代码引用一个实体时,SDK 给出这个实体在本地最新的版本,并冻结这个版本。
- 冻结一个版本前,SDK 先冻结它引用的、仍是草稿的版本。所以冻结的顺序满足依赖顺序,SDK 按这个顺序发送,见 VERSION-13。
提交策略
同一个 Observer 可以对不同的业务类型选择不同的策略。两种策略都在数据形成时本地保存草稿,补全合并进草稿;区别在于草稿何时冻结。
| 策略 | 何时冻结 | 后端看到的版本 |
|---|---|---|
| 及时提交 | SDK 每次发送时冻结当前的草稿;之后的补全形成新的草稿 | 每次发送一个版本,最后一个是最终结果 |
| 结束后提交 | 业务代码通知结束时冻结 | 只有最终结果 |
发送的时机不影响事实时间。
更新与结束
业务代码把新形成或补全的数据交给 SDK,并明确通知 SDK 一段事实何时结束。endAt 有值不能当作结束通知,因为它也可能只是最近一次确认的时刻,见 TIME-4。
例如 10:00 确认 Codex 在前台,10:05 再次确认,业务代码把同一份 Observation 补全到 10:05。10:08 切换应用时,业务代码才通知这一段结束,并为新应用形成新的观测。
发送与确认
本地保存与发送
SDK 先把版本保存在本地,再安排发送。它按冻结的顺序把版本分批发送,一批就是一次提交,大小在请求体的上限之内。发送失败或响应不确定时,版本留在本地,等网络恢复或程序重启后继续发送。
SDK 启动时恢复草稿、待发送的版本和待处理的被拒绝版本;退出前只需把已接收的内容保存到本地。
确认
SDK 按响应确认版本,不需要读回。
| 响应 | SDK 的处理 |
|---|---|
200 | 这一批全部确认为已保存 |
400 或 409,带 index | 位置在 index 之前的版本确认为已保存;index 处的版本被拒绝;之后的版本确定没有保存,可以由业务代码改写 |
400,不带 index | 请求不符合契约,是 SDK 的错误:停止发送,报告给业务代码 |
413 | 拆成更小的批,重新发送 |
5xx 或网络失败 | 稍后原样重发 |
保存成功不代表这段事实已经结束,SDK 继续接收更新和结束通知。状态码和错误字段见 API · 错误格式。
重试
重试沿用原来的 VersionId,内容、引用和事实时间保持原样,见 IDENTITY-5。后端已保存但响应丢失时,SDK 原样重发,后端把已保存的版本直接确认,返回首次保存时的 recordedAt,见 VERSION-7。
被拒绝的版本
SDK 把被拒绝的版本、它的 rule,以及依赖它的待发送版本交给业务代码,处理完之前暂停发送。业务代码可以:
- 丢弃这个版本,以及依赖它的版本;
- 读取实体新的当前版本,基于它改写这个版本,以及依赖它的版本。
因 VERSION-6 被拒绝、而后端的当前版本与被拒绝的版本内容相同时,业务代码可以让 SDK 采用已保存的版本:SDK 把待发送版本中指向被拒绝版本的引用改为指向当前版本,再继续发送。例如同一台设备上的两个程序实例共用本机应用的登记文件,同时为同一个应用提交首版,后提交的一方采用先保存的首版。
400 带 rule 的拒绝说明版本本身违反 Core 规则,通常是业务代码的错误,需要修正后再处理。
发送状态
SDK 向业务代码报告待发送的数量、已确认的数据和被拒绝的原因,业务代码据此区分仍在本地、已保存和被拒绝的数据。
断网示例
前台应用采用及时提交时:
| 时刻 | 发生的事 | SDK 的处理 |
|---|---|---|
| 启动 | 程序实例和所需的 EntitySchema 已确定 | 登记程序实例和 EntitySchema |
| 10:00 | Codex 开始处于前台 | 保存观测内容和 Observation 的草稿,安排发送 |
| 10:02 | 网络中断 | 已冻结的版本留在本地,之后的补全合并进新的草稿 |
| 10:08 | 切换到浏览器 | 冻结 Codex 的最终版本,为浏览器形成新的草稿 |
| 10:10 | 网络恢复 | 按冻结的顺序补交 Codex 的版本,以及浏览器截至最近一次确认的版本 |
如果采用结束后提交,Codex 的时段在结束时冻结并安排发送,浏览器的时段继续作为草稿补全,等业务代码通知结束。
平台实现
同一运行环境中的 Observer 共用该平台的 SDK。本地保存和后台任务的做法写在各平台的页面。
| SDK | 本地保存 |
|---|---|
| .NET | SQLite |