docs(acm2-74): consolidate design documentation
This commit is contained in:
@@ -37,7 +37,7 @@ class JacksonXmlCodec : XmlCodec {
|
||||
mapper.readValue(trimmed, SisMessageXml::class.java)
|
||||
} catch (e: JsonMappingException) {
|
||||
// META 层绑定失败=发送方违反必填契约(MALFORMED);体层(SCHD/FLOP)结构不匹配
|
||||
// =wire 结构超出编解码器当前绑定能力(CODEC_ERROR:退避重试,修复后可重放,design §2.3)。
|
||||
// =wire 结构超出编解码器当前绑定能力(CODEC_ERROR:退避重试,修复后可重放,docs/implementation.md「状态与错误分类」)。
|
||||
val first = e.path.firstOrNull()?.fieldName ?: ""
|
||||
if (first.equals("meta", ignoreCase = true)) {
|
||||
return DecodeResult.Err(DecodeFailure(ErrorClass.MALFORMED, "xml-meta:${e.originalMessage.take(200)}"))
|
||||
|
||||
@@ -35,7 +35,7 @@ internal object SisWireMapper {
|
||||
|
||||
/**
|
||||
* 已落库的 10 类集合:缺席(字段默认空列表)不产生键,出现但为空的元素得到一行空行 `[{}]`;
|
||||
* `filterValues` 去掉的正是"缺席",避免整包凭空清空本地明细(合并语义见 flight-state.md §3.1)。
|
||||
* `filterValues` 去掉的正是"缺席",避免整包凭空清空本地明细(合并语义见 docs/implementation.md「SCHD 日计划」)。
|
||||
*
|
||||
* `SRVT`/`VIPF` 尚未有明细表(`[G-SRVT-VIPF]`):只用"键是否存在"表达段是否出现,保留原始
|
||||
* 内容与顺序,不参与合并、不判断清空语义(`Q13`)——出现(哪怕为空)与缺席不再被抹平。
|
||||
|
||||
@@ -54,10 +54,10 @@ class PipelineProps {
|
||||
*/
|
||||
var backfillMaxAttempts: Int = 100
|
||||
|
||||
/** 回填独立退避的起步间隔 `[G-BACKFILL-BACKOFF]`。 */
|
||||
/** 回填独立退避的起步间隔。 */
|
||||
var backfillBackoffMs: Long = 30_000
|
||||
|
||||
/** 回填独立退避的封顶间隔 `[G-BACKFILL-BACKOFF]`。 */
|
||||
/** 回填独立退避的封顶间隔。 */
|
||||
var backfillBackoffCapMs: Long = 900_000
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ interface DeliveryPort {
|
||||
* 两个主题之间不保证先后顺序。
|
||||
*
|
||||
* 失败处理:队首的重试时间没到就不取;一批里有发送失败,整批重试次数加一并推后退避,
|
||||
* 次数用尽整批转 DEAD 当死信。见 docs/flight-state.md §5。
|
||||
* 次数用尽整批转 DEAD 当死信。见 docs/implementation.md「Kafka 与读取」。
|
||||
*/
|
||||
@Singleton
|
||||
class Dispatcher(
|
||||
|
||||
@@ -12,7 +12,7 @@ enum class EventStatus { PENDING, SENT, DEAD }
|
||||
* 写业务数据时在同一个事务里往这里插一行,投递线程随后按行发出,这样业务提交和"该发的事件"
|
||||
* 不会脱节。KAFKA_SCHD 发完整状态,KAFKA_MSG 只发"这个航班变了"的通知;两个主题之间不保证
|
||||
* 先后顺序。TOMBSTONE 只在两种情况下登记:航班从在用变成删除,或者被生命周期清理前补发
|
||||
* 一次删除通知。见 docs/flight-state.md §5。
|
||||
* 一次删除通知。见 docs/implementation.md「Kafka 与读取」。
|
||||
*
|
||||
* stateVersion 是发布时的航班版本号;同一 FLID 攒了多条待发事件时,只发版本号最新的那条。
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.time.ZoneId
|
||||
* 接收日、也不是落库日,算出来之后就固定不变。
|
||||
*
|
||||
* 民航的一天不一定从零点开始,切日边界由 msgx.operation-day.cutoff-hour 配。默认 0 点只是
|
||||
* 占位,真实口径还要业务确认。见 docs/flight-state.md §2.1。
|
||||
* 占位,真实口径还要业务确认。见 docs/implementation.md「航班身份与运营日」。
|
||||
*/
|
||||
class OperationDayCalculator(
|
||||
zone: ZoneId,
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.time.LocalDate
|
||||
*
|
||||
* 只追加不修改;写失败不影响主流程,只记一条指标。数据丢了可以靠重放重建,它也不参与任何
|
||||
* 状态决策。一行对应一次处理尝试,被重放的包会再记一行。默认保留 90 天,按(快照覆盖截止日,
|
||||
* 接收时间)清理。见 docs/flight-state.md §2、docs/design.md §6.2。
|
||||
* 接收时间)清理。见 docs/implementation.md「生命周期与清除」。
|
||||
*/
|
||||
enum class SnapshotResult { COMMITTED, REPLAY_SKIPPED, ROLLED_BACK }
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
* 航班实例当前态模型,规则见 docs/flight-state.md §2/§3。
|
||||
* 航班实例当前态模型,规则见 docs/implementation.md「航班域」。
|
||||
*
|
||||
* FLID(航班实例 ID)是航班唯一的关联键,同一航班的所有报文都靠它对上号。OPERATION_DAY
|
||||
* (运营保障日)在航班首次入库时确定,之后不允许再改;日计划还没收录它之前可以是 NULL。
|
||||
@@ -28,7 +28,7 @@ data class FlightMainRow(
|
||||
*
|
||||
* scalars 和 collections 只装报文里真正出现过的字段:出现就覆盖本地值(标量给空串表示
|
||||
* 显式清空),没出现就保留库里已有的值;集合一旦出现就按合并后的完整结果整体覆盖写入。
|
||||
* 合并规则见 docs/flight-state.md §3.1。
|
||||
* 合并规则见 docs/implementation.md「SCHD 日计划」。
|
||||
*/
|
||||
data class ScheduleRecord(
|
||||
val flid: String,
|
||||
@@ -40,7 +40,7 @@ data class ScheduleRecord(
|
||||
* FLOP/ADFT 报文的增量载荷:只带这次要改的字段和集合,没出现的字段保留库里已有的值。
|
||||
*
|
||||
* ADFT 里缺失字段到底算清空还是算保留,上游还没给准话,所以暂时保守处理成"只设不改",
|
||||
* 不按整包替换来理解。见 docs/flight-state.md §3.2/§3.3。
|
||||
* 不按整包替换来理解。见 docs/implementation.md「动态运行事件」「删除与重建」。
|
||||
*/
|
||||
data class MergeChange(
|
||||
val flid: String,
|
||||
@@ -79,7 +79,7 @@ data class HistoryRules(
|
||||
* wasNeverFdel 表示这个航班从没收到过 FDEL(航班终止报文),是被生命周期直接清掉的,
|
||||
* 所以清除前要补发一次删除通知,否则下游不知道它已经没了。目前只能靠推断:没有地方记录
|
||||
* "曾经收过 FDEL",于是把 state = ACTIVE 当成没收到过。副作用是 FDEL 之后又被 ADFT
|
||||
* 重新激活的航班会被误判、重复补发,解决办法待定(见 docs/flight-state.md §6 开放项)。
|
||||
* 重新激活的航班会被误判、重复补发,解决办法待定(见 docs/implementation.md「生命周期」)。
|
||||
*/
|
||||
data class HistoryCandidate(
|
||||
val flid: String,
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.time.LocalDate
|
||||
* 没出现的保留,标量给空串表示清空;已删除的航班保持删除,日计划救不回来;
|
||||
* - [mergedState]:把 FLOP/ADFT 的增量合并进当前态,只改报文明确表达的字段和集合。
|
||||
*
|
||||
* 合并规则见 docs/flight-state.md §3.1/§3.2。
|
||||
* 合并规则见 docs/implementation.md「SCHD 日计划」「动态运行事件」。
|
||||
*/
|
||||
object FlightStateEngine {
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.time.ZoneId
|
||||
// 同一个事务里,并且进事务后第一件事就是拿 PIPELINE_LOCK 这把单行锁。锁只负责让
|
||||
// 写事务排队,不负责选主或故障切换。
|
||||
//
|
||||
// 航班模型与合并规则的完整说明见 docs/flight-state.md。
|
||||
// 航班模型与合并规则的完整说明见 docs/implementation.md「航班域」。
|
||||
// =====================================================================
|
||||
|
||||
/** 把一段代码包进一个数据库事务:航班状态、待发事件、处理终态要么一起提交,要么一起回滚。 */
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ import javax.sql.DataSource
|
||||
// 自有 PostgreSQL 的仓储实现,表结构见 db/migration/V1__flight_state_baseline.sql。
|
||||
//
|
||||
// 航班相关的写操作都要求调用方先开事务、再拿 PIPELINE_LOCK 单行锁(见 Repositories.kt
|
||||
// 顶部说明)。航班模型与合并规则见 docs/flight-state.md。
|
||||
// 顶部说明)。航班模型与合并规则见 docs/implementation.md「航班域」。
|
||||
// =====================================================================
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -8,7 +8,7 @@ import jakarta.inject.Singleton
|
||||
* 投递端口的内存假实现,只有配置 msgx.stubs=true 时才装配,本地开发和测试用。
|
||||
*
|
||||
* 每次发送都往 sent 里记一条(topic、key、payload);payload 为 null 的那条就是删除通知
|
||||
* (tombstone,下游按"这个键没了"理解成删除)。见 docs/flight-state.md §5。
|
||||
* (tombstone,下游按"这个键没了"理解成删除)。见 docs/implementation.md「Kafka 与读取」。
|
||||
*/
|
||||
@Requires(property = "msgx.stubs", value = "true")
|
||||
@Singleton
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
* 收报轮询会按 ID 把它补进来,所以不会丢消息。
|
||||
*
|
||||
* 该入口**不参与水位**:它直接写 PROC_STATE,登记的行可能超出水位;主泵只领 `msgId ≤ W`,
|
||||
* 因此不破坏 FIFO——这类行等水位追平后按序自然领取(`invariants.md` INV-4)。
|
||||
* 因此不破坏 FIFO——这类行等水位追平后按序自然领取(`specification.md` `INV-4`)。
|
||||
*/
|
||||
@Singleton
|
||||
class InboxService(
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.time.Instant
|
||||
* 5. 归档失败或者结果说不清的,原样留着下次再来。
|
||||
*
|
||||
* 红线:历史存储没接通时必须一条都不删。先删当前态、事后再补历史,是不允许的。
|
||||
* 见 docs/flight-state.md §6。
|
||||
* 见 docs/implementation.md「生命周期」。
|
||||
*/
|
||||
@Singleton
|
||||
class HistorySweepJob(
|
||||
|
||||
@@ -84,7 +84,7 @@ class Pump(
|
||||
//
|
||||
// 水位以内的行都是收报按 ID 顺序发现并登记的;水位之外的行只可能来自兼容入口
|
||||
// 直接写 PROC_STATE(它不参与水位)。若允许领取,它就会越过那些尚未入队的较小 ID,
|
||||
// 破坏 FIFO(不变量"只领取已发现的行",`invariants.md` INV-4)。这种行在空洞补齐、`W` 追平之后自然可领取。
|
||||
// 破坏 FIFO(不变量"只领取已发现的行",`specification.md` `INV-4`)。这种行在空洞补齐、`W` 追平之后自然可领取。
|
||||
val watermark = cursor.load().committedUpTo
|
||||
if (head.msgId > watermark) {
|
||||
warnBeyondWatermark(head.msgId, watermark)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# 口令/端点全部环境变量外置——零入库。
|
||||
# 存储边界:自有 PostgreSQL(datasources.default,全部内部状态与运营航班权威 FLIGHT_SCHD/SCHD_GEN)+
|
||||
# 共享 MySQL 信箱(mailbox.shared-mysql,仅 DML,不建表)+
|
||||
# Kafka + Eureka/logstash。权威设计:docs/flight-state.md。
|
||||
# Kafka + Eureka/logstash。权威设计:docs/implementation.md。
|
||||
# 键位口径:Micronaut 5.1(U03):datasources.* / flyway.datasources.* /
|
||||
# kafka.producers.<name>.* / eureka.client.*(Micronaut 原生键)。
|
||||
# =====================================================================
|
||||
@@ -18,26 +18,26 @@ msgx:
|
||||
max-attempts: 5 # 处理/投递同值
|
||||
backoff-ms: [1000, 2000, 4000, 8000] # 指数退避;档位数必须 = max-attempts - 1(启动自检)
|
||||
backoff-cap-ms: 60000
|
||||
max-commit-delay: 5m # §5.1 空洞老化:W+1 空洞超过该时延判定为永久(Q2 最大提交时延)
|
||||
overdue-backfill: 30d # 超期补写期限 R(Q6):仅须 R ≤ R_keep,不保护重放窗口;判据比较本地 ENQUEUED_AT(design「回填」)
|
||||
max-commit-delay: 5m # 空洞老化:W+1 空洞超过该时延判定为永久(Q2 最大提交时延)
|
||||
overdue-backfill: 30d # 超期补写期限 R(Q6):仅须 R ≤ R_keep,不保护重放窗口;判据比较本地 ENQUEUED_AT(implementation.md「回填」)
|
||||
backfill-batch: 100 # 回填扫描单批条数
|
||||
backfill-max-attempts: 100 # 单行重试的告警阈值;放弃判据是 R 超期,不是次数(design「回填」)
|
||||
backfill-max-attempts: 100 # 单行重试的告警阈值;放弃判据是 R 超期,不是次数(implementation.md「回填」)
|
||||
# 一次性切流播种:默认(注释掉)不播种。min=读现存全部 | zero=从 0 按空洞规则 | max=跳过可见存量 | <id>
|
||||
# cutover-watermark: min
|
||||
autostart: false # U07:启动即拉起 Pump/Dispatcher 循环;需真实仓储或 msgx.stubs=true 才开启(dev 见 application-dev.yml)
|
||||
schd:
|
||||
flush-period: 3s # KEEP 现役推送节律
|
||||
flush-limit: 500 # 批上限,防积压尖峰
|
||||
operation-day: # §3.5:SODT + 机场时区 + 切日边界(默认占位 0 点,待业务确认 §10)
|
||||
operation-day: # SODT + 机场时区 + 切日边界(默认占位 0 点,待业务确认)
|
||||
zone: Asia/Shanghai
|
||||
cutoff-hour: 0
|
||||
history: # §8:历史判定窗口与保留期
|
||||
history: # 历史判定窗口与保留期
|
||||
cancelled-hours: 48
|
||||
terminal-hours: 48
|
||||
deleted-hours: 48
|
||||
idle-hours: 168
|
||||
snap-log-retention-days: 90
|
||||
history-store-enabled: false # 未接通历史存储时 HISTORY_SWEEP 删 0 条(§8.2)
|
||||
history-store-enabled: false # 未接通历史存储时 HISTORY_SWEEP 删 0 条
|
||||
|
||||
micronaut:
|
||||
application:
|
||||
@@ -78,7 +78,7 @@ flyway:
|
||||
# 处理回填;出站写 COUTMSGS;不建表/schema,ACM2-12)。驱动/依赖与信箱适配层
|
||||
# (CminmsgMailbox/OutboxMailbox)随 U05 批次引入。
|
||||
mailbox:
|
||||
processed-value: PROCESSED # §5.2 处理标记写入值(Q7:值集与写权限以库方契约为准)
|
||||
processed-value: PROCESSED # 处理标记写入值(C-5/Q7:值集与写权限以库方契约为准)
|
||||
shared-mysql:
|
||||
enabled: false
|
||||
url: ${MSGX_MAILBOX_URL}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- 升级路径:已应用过 V1–V10 的库无法直接升级(版本链与校验和都对不上)。重建 schema
|
||||
-- 或删除数据卷后重跑迁移;禁止对生产库手工 repair 或改写 flyway_schema_history。
|
||||
--
|
||||
-- 分层(职责与写者见 docs/flight-state.md、docs/design.md):
|
||||
-- 分层(职责与写者见 docs/implementation.md「航班域」):
|
||||
-- · 决策层:FLIGHT_SCHD + 8 张资源明细表 + FLIGHT_ROUTE_POINT —— 权威当前态(INV-11);
|
||||
-- · 管道层:PIPELINE_LOCK / INBOX_CURSOR / PROC_STATE / MSG_EVENT / REQ_TRACK;
|
||||
-- · 留痕层:SCHD_SNAP_LOG —— 只追加、可重建、不参与决策;
|
||||
@@ -294,7 +294,7 @@ CREATE TABLE MSG_EVENT (
|
||||
ERROR_CLASS VARCHAR(20),
|
||||
LAST_ERROR VARCHAR(1000),
|
||||
CREATED_AT TIMESTAMP(6) WITH TIME ZONE NOT NULL,
|
||||
SENT_AT TIMESTAMP(6) WITH TIME ZONE -- 投递确认的同一条 UPDATE 内写入;是保留期判定的唯一基准(G-EVENT-RETENTION)
|
||||
SENT_AT TIMESTAMP(6) WITH TIME ZONE -- 投递确认的同一条 UPDATE 内写入;是保留期判定的唯一基准
|
||||
);
|
||||
CREATE INDEX idx_evt_head ON MSG_EVENT (TARGET, STATE, EVENT_ID); -- 每 target 队头
|
||||
-- KAFKA:schd 按 FLID 单行(只保留最新 STATE_VERSION);KAFKA:msg 仍是多行 append-log,不受此约束。
|
||||
|
||||
@@ -7,7 +7,7 @@ Flyway 配置**;PG 路径使用 `classpath:db/migration`,两者互不混用
|
||||
|
||||
1. 现场 11.2 补丁级别、数据库字符集、DBA 权限清单拿到,且可提供可测试的目标库。
|
||||
2. JDK 25 × ojdbc 驱动(具体版本)× Flyway Oracle 支持 × 连接池组合在目标库实测通过
|
||||
——不能以"PG 通过"代替 Oracle 验收(Oracle 11g 适配为开放项,见 `docs/flight-state.md`)。
|
||||
——不能以"PG 通过"代替 Oracle 验收(Oracle 11g 适配为开放项,见 `docs/implementation.md`)。
|
||||
3. 11g 的 upsert(MERGE INTO)与绑定顺序适配完成并通过与 PG 同粒度的集成测试后,
|
||||
才允许把仓储 SQL 切到 11g 方言。先前编译级交付的 `SqlDialect` 方言接缝已随
|
||||
基线列名更替(fday/last_message_id → operation_day/last_msg_id)退役删除;
|
||||
|
||||
Reference in New Issue
Block a user