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)
|
||||
|
||||
Reference in New Issue
Block a user