Files
msgexchange-v2/docs/flight-state.md
T

128 lines
11 KiB
Markdown
Raw Normal View History

# 运营航班状态设计
本文是航班状态的唯一现行设计规范。它定义权威数据、处理语义、生命周期和对外投递;其他文档只描述管道或产品验收,不重复定义航班状态规则。
2026-09-09 11:43:34 +08:00
## 1. 目标与边界
系统从共享 MySQL 信箱接收 SIS/AODB 报文,把结果合并到自有 PostgreSQL 中的航班当前态,再通过 outbox 投递 Kafka。共享信箱和 Kafka 都不是状态权威,也不在本地事务的提交范围内。
- `FLID` 是航班实例的唯一标识;不得由航班号、日期或资源号推断身份。
- `FLIGHT_SCHD` 及其明细表是唯一权威当前态;展示视图只读,不能作为写入或对账来源。
- 单活动主泵按信箱 FIFO 推进。事务内 `PIPELINE_LOCK` 只串行化本地状态提交,不替代选主或消息认领。
- 状态写入、outbox 事件、处理终态和回填意图在同一 PostgreSQL 事务中提交;回填与 Kafka 投递在提交后独立重试。
现场目标库为 Oracle 11g;当前已验证的实现基准是 PostgreSQL。Oracle 适配未完成前,不能把它视为可切换的运行时选项。
## 2. 权威模型
| 对象 | 职责 |
|---|---|
| `FLIGHT_SCHD` | 一行一个 `FLID`,保存标量字段、`STATE``STATE_VERSION``OPERATION_DAY`、最近消息 ID 和审计时间。 |
| 资源明细表 | 当前 8 张表保存登机门、值机柜台、转盘、计划机位、滑槽、延误、靠撤桥、轮挡等变长集合;`SRVT`/`VIPF` 专用明细尚未实现 `[G-SRVT-VIPF]`。主键为 `(FLID, ORDINAL)`。 |
| `FLIGHT_ROUTE_POINT` | ROUT 与 ERUT 两类路线点,使用 `ROUTE_KIND` 区分;主键应包含该列,避免两类路线的序号冲突。 |
| `PROC_STATE` | 信箱消息的处理终态、业务身份幂等记录,以及回填事实(`RECEIVED_AT` / `BACKFILL_*`)。 |
| `MSG_EVENT` | 事务 outbox,承载整态投影、变更通知和删除 tombstone。 |
| `INBOX_CURSOR` | 共享信箱消费水位(读取进度,与处理标记互不替代)。 |
| `SCHD_SNAP_LOG` | 日计划处理留痕,只追加、可重建,不参与状态决策。 |
### 2.1 航班身份与运营日
`FLID` 是主键。`OPERATION_DAY` 从 SCHD 记录的 `SODT` 按配置的机场时区和切日规则推导;它不是消息接收日或落库日。
一旦已写入非空 `OPERATION_DAY`,同一 `FLID` 不得改到另一个运营日。遇到冲突,整包日计划按协议错误拒绝,既有状态保持不变。尚未由日计划收录的航班可以为 `NULL`;这不表示该航班没有运营日,只表示当前模型无法为它确定归属日。
### 2.2 字段与集合
标量与异常对象前缀字段存于主表。协议中的 `SRVT``VIPF` 是无界集合,目标形态必须按集合完整保存到专用明细表示;当前只在 wire/domain 保留其出现事实与原始内容,专用明细、合并与投递尚未实现 `[G-SRVT-VIPF]``MAFL` 不是 SIS/XML 入站字段,而是由共享航班的 `MAID``FLID``FLNO` 生成的主航班派生投影;当前尚未实现 `[G-MAFL]`
- `ORDINAL` 是持久化顺序,从 1 开始;`SOURCE_SEQ` 是上游序号,允许为空或重复。
- 相同资源号不代表同一条分配,禁止按资源号去重。
- 每次持久化完整航班状态时,明细表按该 `FLID` 先删后插,以完整合并结果为准。
- ROUT 与 ERUT 是两类独立集合,不能因相同序号覆盖彼此。
- 主/共享关系以主表的 `MAID` 为事实来源:`MAID` 是共享航班指向主航班 `FLID` 的引用(非共享航班为 `NULL`);`MAFL` 只在读取和事件投影时从子航班事实派生,不按入站标量解析或保存。
### 2.3 主/共享投影(`MAFL`
`MAFL` 是主航班的派生集合,元素为子航班的 `FLID``FLNO`;内容与变更传播分别由 `INV-21``INV-22` 保证。
- 子航班集合 = `STATE = ACTIVE``MAID = 主航班 FLID``FLIGHT_SCHD` 行;已 FDEL 的子航班(`STATE = DELETED`)自然退出投影,不需要改写主航班行。
- 只有 `MAID` 为空的主航班携带 `MAFL`;共享航班只携带自身 `MAID``CSOP``CSFT`,不携带 `MAFL`,避免下游双向合并。
- 投影按 `FLID` 升序,与到达顺序及 `FLNO` 变更无关:同一 `STATE_VERSION` 的投影逐字节稳定,重发与消费端比对才有意义。
- `MAID = FLID` 的自引用行不进入任何 `MAFL``MAID` 指向不存在主航班的悬挂引用不阻断该子航班自身处理,只是不产生投影。
- 子航班集合变化(新增、删除、`MAID` 迁移)必须让涉及的主航班在同一事务内推进 `STATE_VERSION` 并登记主航班事件(`KAFKA:msg` + `KAFKA:schd`);否则整态投影的只进不退写入会丢弃它(`design.md``schd` 聚合」)。共享航班自身不单独发通知。
- 派生主航班投影与产生它的状态写入必须同一事务或一致读快照;按 `MAID` 取子航班要求该列有索引(`INV-17`)。
## 3. 合并与写入语义
领域决策逻辑(如 `FlightStateEngine` 及各类 Handler 规则)保持纯粹:它根据当前完整态和已解码报文,返回下一完整态与待发事件,不执行数据库或 Kafka I/O。`ScheduleProcessor` / `FlopProcessor` / `FdelProcessor` / `AdftProcessor` 是事务协调器,负责在统一事务边界内调用决策逻辑并持久化结果。
### 3.1 SCHD 日计划
SCHD DNLD/RESP 在整包校验通过后,逐条将报文携带的航班写入当前态。日计划只更新或创建其携带的 `FLID`,**不会因其他航班未出现在本次报文中而删除任何记录**;SIS 同向(§3.16 注释 1 要求子系统自行保留前一日延误航班)。
日计划在重叠字段上可以覆盖当前动态值;未携带的字段按合并规则保留,显式清空才清除。每个成功写入的航班推进 `STATE_VERSION`,并在同一事务登记 `KAFKA:schd``KAFKA:msg` 事件。
**字段缺失语义与外部规范冲突**:SIS 规定最新日计划中未发送的可选字段表示 AODB 已无该数据、子系统应删除本地已有值(`SIS_AODB_RMS-V0.1.md` §3.16 注释 4RESP 与 DNLD 同格式,见 §3.17),并要求以 AODB 最新数据覆盖本地(SIS §1.6.2)。这与上面的"未携带字段保留"相反。确认前两条并存,按 Q13 跟踪,不得据本节推定已与上游对齐。
消息重放由 `PROC_STATE` 的消息 ID 与 `identity_key` 控制;已成功提交的消息不得再次写入或重复登记事件。整包校验失败或运营日冲突时,整包不落地。
DNLD、RESP 和 ADFT 均已有路由入口。RESP 的请求匹配闭环、出站请求与超时重发仍是待交付项;未注册处理器不能被视为已实现。
### 3.2 动态运行事件
FLOP 事件只修改它表达的字段或资源集合,其余航班状态保持不变。每个动态子类型的语义都必须有明确 Handler 规则和回归测试,不能只因已被路由就推定其业务语义完整。
动态事件保留既有 `OPERATION_DAY`,也不基于接收时间重新推导它。未知或已删除航班的具体处理遵从对应 Handler 的幂等规则。
### 3.3 删除与重建
FDEL 是业务删除入口:仅在 `ACTIVE → DELETED` 时推进版本、保留明细并与 tombstone 同事务登记;重复 FDEL 或不存在的航班按幂等成功处理。
物理删除仅由独立历史清理在归档成功后执行。日计划报文不是删除依据。若未经 FDEL 而由生命周期清理,清理前需要登记一次 tombstone;已经 FDEL 的记录不重复发出。
ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-only 规则:出现字段可更新,缺失字段不清空;不得把它当成日计划或动态全量替换。新建 ADFT 若带可解析的 `SODT`,按同一运营日规则计算 `OPERATION_DAY`;否则保留为 `NULL`
主/共享航班级联:删除共享航班时重算主航班 `MAFL`(见「主/共享投影」)并向主航班通知;删除主航班时级联删除其子共享关联并发出删除通知;主/共享关系必须一次原子变更,不出现主已删、子残留的半状态。共享航班增量通常只更新并通知主航班,不直接发共享通知。这些语义同样约束 FDEL 之外的生命周期清理。主/共享关联的增删按 `FLID` 做值比较,不使用引用比较。
SIS 规定删除主航班时必须先删子共享航班、再删主航班,顺序不符时 RMS 应向 AODB 回发 EROR`SIS_AODB_RMS-V0.1.md` §1.6.1-1.d,事件定义见 SIS §4.8)。本文的原子级联不发该回报,两者取舍见 Q14。
## 4. 处理事务与失败规则
所有状态处理遵循下列顺序:
1. 主泵只处理 FIFO 队头,解码并绑定业务身份。
2. 校验消息结构、声明数量、航班标识和运营日;协议错误标记 `DEAD`,不提交半包。
3. 在一个事务中取得 `PIPELINE_LOCK`,读取当前完整态,计算下一状态,写主表和明细表,登记 outbox 和回填意图。
4. 同一事务提交处理终态;任一步失败则整体回滚并按错误类别重试或终止。
5. 提交后回填共享信箱并异步投递 outbox;后续失败不得把已提交的 `SUCCEEDED` 改回失败。
`identity_key``发送方|类型|子类型|序号` 构成,用于业务重复检测。`STATE_VERSION` 是单航班的单调版本,供下游判定新旧;它不是日计划版本,也不表示运营日版本。
## 5. Kafka 与读取
`KAFKA:schd` 是按 `FLID` 的完整状态投影。Dispatcher 可以合并同一 `FLID` 尚未投递的中间版本,只发最新状态;消费端用 `(FLID, STATE_VERSION, UPDATED_AT)` 防止旧投影覆盖新状态。
`KAFKA:msg` 只通知变化,不承载权威状态;两个 topic 不承诺顺序。FDEL 和必要的生命周期清理使用 tombstone:键为 `FLID`,删除记录以 null 值投递,通知下游移除旧状态。
读取完整航班需要读取主表和全部明细。当前逐航班读取有多次查询,尚未保证跨表一致性快照;批量加载与明确的一致性读边界是后续优化项。
## 6. 生命周期与开放项
运营日过去不等于航班结束。历史清理须同时满足配置保留期与终态证据或足够静默期,先成功写入历史存储,后物理删除当前态;历史存储失败时必须删除零行。
以下事项仍需确认或交付:
- Oracle 11g 的完整方言与集成验证;
- RESP 请求—应答匹配和出站请求重发;
- ADFT 缺失字段和 `FLID` 重用的上游语义(`FLID` 复用见 `Q16`);
- 日计划缺失可选字段的删除语义(与 SIS 的冲突见本文件「合并与写入语义」,`Q13`);
- 主/共享删除顺序与 EROR 回报义务(Q14);
- `MAFL` 投影是否出现在查询视图,随 `Q3`;字段名与空集合表示随 `Q4`
- 未归属 `OPERATION_DAY` 航班的终止与保留策略;
- ROUT/ERUT 联合主键迁移;
- 批量一致性读取,以及动态事件仅在事务内计算一次的收敛。
## 7. 不变量
航班域不变量的**定义处是 [invariants.md](invariants.md)**`INV-11``INV-20`),本节不再重复。