diff --git a/docs/README.md b/docs/README.md index bc6bd9403..384c00cfc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,16 +13,11 @@ | 开发工具 | [development.md](./development.md) Make 开发、启动与质量命令 | 有效 | | 设计/架构 | [design/relational-r2dbc.md](./design/relational-r2dbc.md) R2DBC 关系访问层 | 已落地,待全量实库/拓扑验收 | | 质量/测试 | [r2dbc-migration-test-handoff.md](./r2dbc-migration-test-handoff.md) R2DBC 全平台测试交接 | 待执行,发布硬门禁 | -| 质量/测试 | [dc3-web-low-defects-handoff.md](./dc3-web-low-defects-handoff.md) dc3-web 遗留缺陷修复交接 | LOW 项已修复;「需人工确认」5 项待裁决 | -| 设计/架构 | [design/tsdb-abstraction.md](./design/tsdb-abstraction.md) 时序存储 Port | 已落地 | -| 设计/架构 | [design/mq-abstraction.md](./design/mq-abstraction.md) 消息中间件 Port | 已落地 | -| 设计/架构 | [design/storage-abstraction.md](./design/storage-abstraction.md) 存储抽象总纲 | 部分被取代(§3/§4) | | 设计/架构 | [design/mcp-runtime-overhaul.md](./design/mcp-runtime-overhaul.md) MCP 运行时重构 | 提案,未实施 | | 设计/架构 | [design/dc3-client-sdk.md](./design/dc3-client-sdk.md) 客户端 SDK | 提案(phase 0 已落地) | | 设计/架构 | [design/frontend-three-terminal-ux.md](./design/frontend-three-terminal-ux.md) 前端三终端 UX ADR | ADR | | 分析对比 | [comparison-dgiot.md](./comparison-dgiot.md) DG-IoT 对比借鉴 | 开放评审,未排期 | | 分析对比 | [comparison-supos-free.md](./comparison-supos-free.md) supOS-Free 对比借鉴 | 开放评审,未排期 | -| 分析对比 | [dc3-thing-model-review.md](./dc3-thing-model-review.md) 物模型代码级核实 | 事实判定(非路线图) | --- @@ -54,14 +49,11 @@ ## 二、设计与架构文档(design/) -### 存储演进主线(取代链,先看这张图再读文档) +### 存储演进主线 -```text -storage-abstraction.md(2026-08-17 总纲) - ├─ §1-2 三层存储模型、§6 统一配置面 .......... 仍有效(总纲骨架) - ├─ §3 关系方言机制(MyBatis databaseId 双方言)──已落地 R1/R2,后继──→ relational-r2dbc.md(R2DBC 重写,已批准) - └─ §4 时序存储 Port .......................... 已被取代──→ tsdb-abstraction.md(已落地) -``` +存储/时序/MQ 三个抽象设计(storage-abstraction、tsdb-abstraction、mq-abstraction)已全部落地并退役: +语义现由源码与 TCK 门禁守护(四 MQ 适配器、PostgreSQL TSDB 实现、单关系方言)。演进链的终点是 +[relational-r2dbc.md](./design/relational-r2dbc.md) —— 现行关系访问层的唯一设计基线。 ### 4. [基于 Spring Data R2DBC 的关系访问层设计](./design/relational-r2dbc.md) @@ -75,34 +67,19 @@ storage-abstraction.md(2026-08-17 总纲) - **读什么**:当前已证实与未证实边界、已知阻断问题、全 Store 实库矩阵、数据库组合、分页/流式协议和发布硬门禁。 - **注意**:当前单元/TCK 基线不能替代本文要求的全链路证据;任一必测项失败、跳过或无证据都阻断发布。 -### 5. [时序存储抽象(TSDB Port)](./design/tsdb-abstraction.md) - -- **一句话**:关系与时序统一为 PostgreSQL/TimescaleDB + R2DBC,`dc3-tsdb-core` 定义历史 Port,单一实现由 Data 域承载并接受 PostgreSQL TCK。 -- **读什么**:port 必须承载的语义(§4)、核心 API(§6);能力差异的落地细节对应 [tsdb-stores.md](./tsdb-stores.md)。 - -### 6. [消息中间件抽象(MQ Port)](./design/mq-abstraction.md) - -- **一句话**: center ↔ driver 异步平面的 broker 抽象,`dc3-mq-core` + 四适配器(RabbitMQ/Kafka/Pulsar/MQTT 5)+ TCK,已交付。 -- **读什么**:核心 API(§6)、订阅模式与目的地映射(§7)、逐 broker 的硬语义(§8:延迟消息、死信、MQTT 两平面分离)。 - -### 7. [统一 PostgreSQL R2DBC 存储设计](./design/storage-abstraction.md) - -- **一句话**:统一 PostgreSQL、TimescaleDB、R2DBC、JSONB、UTC 时间、租户隔离和事务边界的现行总纲。 -- **读什么**:运行时拓扑、数据格式、分页、并发、取消和发布门禁。 - -### 8. [MCP 运行时全面重构:内聚的授权契约](./design/mcp-runtime-overhaul.md) *提案* +### 5. [MCP 运行时全面重构:内聚的授权契约](./design/mcp-runtime-overhaul.md) *提案* - **一句话**:现状一次 `tools/call` 要三次网关→auth 冗余往返且有正确性缺口;目标一次内聚往返、响应式非阻塞、真实 input schema、异步审计。 - **读什么**:§2.3 的三冗余一缺口(问题陈述最精彩)、§5 目标契约、§9 备选方案。 - **关联**:实施依赖 relational-r2dbc 的 auth 响应式改造节奏。 -### 9. [dc3-sdk:框架无关客户端 SDK](./design/dc3-client-sdk.md) *提案(phase 0 已落地)* +### 6. [dc3-sdk:框架无关客户端 SDK](./design/dc3-client-sdk.md) *提案(phase 0 已落地)* - **一句话**:网关 HTTP 契约现在被 dc3-web(30 个 api 模块)和 dc3-cli(手写 fetch)重复实现,未来 native app 会是第三份; 目标一个 TypeScript SDK 实现一次,三端消费。 - **读什么**:边界规则(可 lint 强制)、迁移计划;依赖前端 ADR 的边界纪律。 -### 10. [前端三终端 UX 架构(ADR)](./design/frontend-three-terminal-ux.md) +### 7. [前端三终端 UX 架构(ADR)](./design/frontend-three-terminal-ux.md) - **一句话**:dc3-web 从桌面优先(1280px 硬底线)转向桌面/平板/手机三终端架构,用公理化推导替代零散 `@media` 补丁。 - **读什么**:公理、分层模型、断点契约、边界复用纪律——前端任何新页面开工前都该过一遍。 @@ -111,19 +88,14 @@ storage-abstraction.md(2026-08-17 总纲) ## 三、分析与对比(参考性,未进实施排期) -### 11. [DG-IoT 与 IoT DC3 对比借鉴](./comparison-dgiot.md) +### 8. [DG-IoT 与 IoT DC3 对比借鉴](./comparison-dgiot.md) - **一句话**:跨项目对比提炼可借鉴点,并澄清双方物模型差异;开放评审中,**不是承诺路线图**。 -### 12. [supOS-Free 与 IoT DC3 对比借鉴](./comparison-supos-free.md) +### 9. [supOS-Free 与 IoT DC3 对比借鉴](./comparison-supos-free.md) - **一句话**:同上,对标 supOS-Free;结论与 storage/mq 抽象设计互相印证。 -### 13. [DC3 物模型(Profile)代码级核实与最终判定](./dc3-thing-model-review.md) - -- **一句话**:用 grep 全仓 + 逐文件读源码的方式回答"DC3 的 Profile 是否等价于物模型",证据落到类/方法/表名,给出最终判定。 -- **价值**:回答"DC3 有没有物模型"这一常见问题的权威出处。 - --- ## 按任务找文档(推荐阅读路径) @@ -131,8 +103,8 @@ storage-abstraction.md(2026-08-17 总纲) | 你想做什么 | 按顺序读 | |------------|----------| | 部署 / 换库 / 换 broker | [db-dialects](./db-dialects.md) → [tsdb-stores](./tsdb-stores.md) → [mq-brokers](./mq-brokers.md) | -| 理解存储层现状与演进 | [storage-abstraction §1–2](./design/storage-abstraction.md) → [tsdb-abstraction](./design/tsdb-abstraction.md) → [db-dialects](./db-dialects.md) → [relational-r2dbc](./design/relational-r2dbc.md) | +| 理解存储层现状与演进 | [db-dialects](./db-dialects.md) → [relational-r2dbc](./design/relational-r2dbc.md) → [tsdb-stores](./tsdb-stores.md) | | 验收全平台 R2DBC 迁移 | [relational-r2dbc 全文](./design/relational-r2dbc.md) → [r2dbc-migration-test-handoff](./r2dbc-migration-test-handoff.md) → 根目录 AGENTS.md | | 前端 / 客户端开发 | [frontend-three-terminal-ux](./design/frontend-three-terminal-ux.md) → [dc3-client-sdk](./design/dc3-client-sdk.md) | | MCP / AI 集成 | [mcp-runtime-overhaul](./design/mcp-runtime-overhaul.md) → [relational-r2dbc §5 D13](./design/relational-r2dbc.md)(OAuth/MCP 聚合拆分) | -| 了解项目定位 / 竞品差异 | [comparison-dgiot](./comparison-dgiot.md) → [comparison-supos-free](./comparison-supos-free.md) → [dc3-thing-model-review](./dc3-thing-model-review.md) | +| 了解项目定位 / 竞品差异 | [comparison-dgiot](./comparison-dgiot.md) → [comparison-supos-free](./comparison-supos-free.md)(物模型能力缺口见 issue #258) | diff --git a/docs/comparison-dgiot.md b/docs/comparison-dgiot.md index 9d1425fd4..0d644d3ef 100644 --- a/docs/comparison-dgiot.md +++ b/docs/comparison-dgiot.md @@ -5,7 +5,7 @@ | **Status** | Analysis / proposal — for reference, not a committed roadmap | | **Date** | 2026-08-18 | | **Scope** | 跨项目对比(DG-IoT ↔ IoT DC3),提炼可借鉴点,并澄清双方物模型差异 | -| **Related** | [comparison-supos-free.md](./comparison-supos-free.md)、[storage-abstraction.md](./design/storage-abstraction.md)、[mq-abstraction.md](./design/mq-abstraction.md) | +| **Related** | [comparison-supos-free.md](./comparison-supos-free.md) | | **Discussion** | 开放评审,未进入实施排期 | > 说明:本文中 **DG-IoT**(`dgiot/dgiot`,gitee: `dgiiot/dgiot`)为国内首款轻量级开源工业物联网平台。 @@ -71,7 +71,7 @@ ### 2.7 数据通道 / 转发 - DG-IoT 的“数据通道”抽象很实用:MQTT/Kafka/HTTP/WebSocket 多通道转发 + MES/WMS/ERP/ESB 多源接入。 -- DC3 有数据桥接驱动 + `mq-abstraction.md`(Proposed),方向一致但 **缺“通道”这一层编排抽象**。 +- DC3 有数据桥接驱动 + 已落地的 MQ 端口抽象(dc3-common-mq,四适配器受 TCK 门禁),方向一致但 **缺“通道”这一层编排抽象**。 ### 2.8 部署与交付 @@ -147,7 +147,7 @@ Device = Driver(协议) + Profile(物模型) ← 设备是「协议接入」 | 优先级 | 借鉴点 | DG-IoT 做法 | DC3 现状 → 落地建议 | |--------|-----------------------------|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------| -| P0 | **数据通道抽象** | MQTT/Kafka/HTTP/WS 多通道转发 + MES/WMS/ERP/ESB 多源接入 | 数据桥接驱动 + `mq-abstraction.md`(Proposed) → 在 MQ 端口之上加一层「通道」编排,落地 Kafka 适配器 | +| P0 | **数据通道抽象** | MQTT/Kafka/HTTP/WS 多通道转发 + MES/WMS/ERP/ESB 多源接入 | 数据桥接驱动 + 已落地的 MQ 端口抽象 → 在 MQ 端口之上加一层「通道」编排,落地 Kafka 适配器 | | P0 | **规则引擎/数据处理增强** | 160+ 函数、清洗/标准化/实时流 | 仅告警规则 → 扩展规则引擎支持数据清洗/转换/转发,复用 `dc3-common-data` | | P1 | **组态页面低代码** | 数据通道→组态全流程低代码 | home 写死 → 可配置 widget 看板 + 组态/大屏 | | P1 | **物模型贯通** | 物模型一次建模,贯通规则/通道/组态 | Profile 独立 → 打通「模型→规则→通道→看板」链路,落成 `profileShareFlag` 模板市场 | diff --git a/docs/comparison-supos-free.md b/docs/comparison-supos-free.md index d7a7e7611..73f33d14e 100644 --- a/docs/comparison-supos-free.md +++ b/docs/comparison-supos-free.md @@ -5,7 +5,7 @@ | **Status** | Analysis / proposal — for reference, not a committed roadmap | | **Date** | 2026-08-18 | | **Scope** | 跨项目对比(supOS-Free ↔ IoT DC3),提炼可借鉴点并落地建议 | -| **Related** | [storage-abstraction.md](./design/storage-abstraction.md)、[mq-abstraction.md](./design/mq-abstraction.md) | +| **Related** | — | | **Discussion** | 开放评审,未进入实施排期 | > 说明:本文中 “superos-free” 即 **supOS-Free**(蓝卓数字科技/中控体系的“工厂操作系统”免费版)。 @@ -79,7 +79,7 @@ #### 3.1 多模态 / 可插拔存储 - **supOS 做法**:关系 + 时序(TDengine/TimescaleDB)+ 非结构化 + 文件多模态底座。 -- **DC3 现状**:[storage-abstraction.md](./design/storage-abstraction.md) 已设计 `dc3.db.type` 关系方言 + +- **DC3 现状**:已落地的存储抽象(`dc3.db.type` 关系方言,flag day 收敛 PostgreSQL)+ `dc3.repository.type` 时序端口(目标 PostgreSQL/MySQL + TimescaleDB/TDengine/InfluxDB/IoTDB),状态为 Proposed — not yet implemented。 - **落地建议**:优先实现 **TDengine 时序适配器**(supOS 主打,国内工业客户常点名),并把 `RepositoryService` @@ -95,7 +95,7 @@ #### 3.3 可插拔消息总线 - **supOS 做法**:EMQX(MQTT)+ Kafka(数据总线)+ Node-RED(边缘计算)。 -- **DC3 现状**:[mq-abstraction.md](./design/mq-abstraction.md) 已设计 `dc3-common-mq` 端口 + 每 broker 一个适配器(RabbitMQ +- **DC3 现状**:已落地的 `dc3-common-mq` 端口 + 每 broker 一个适配器(RabbitMQ 默认,目标 Kafka/RocketMQ/Pulsar),同样 Proposed。 - **落地建议**:先落地 **Kafka 适配器**——工业客户常要求 Kafka 对接大数据平台,是 DC3 在“数据总线”上对标 supOS 的关键一环。 diff --git a/docs/design/mcp-runtime-overhaul.md b/docs/design/mcp-runtime-overhaul.md index eef2956ea..c1b6b7304 100644 --- a/docs/design/mcp-runtime-overhaul.md +++ b/docs/design/mcp-runtime-overhaul.md @@ -6,7 +6,7 @@ | **日期** | 2026-08-18 | | **范围** | MCP 运行时平面:`mcp_runtime.proto`、`McpRuntimeFacade`、`McpGatewayController`,auth 侧的 `OAuthMcpRuntimeServiceImpl` + `McpRuntimeServer` | | **目标** | 每个 MCP 方法一次内聚的 gateway→auth 往返;响应式、非阻塞;真实 input schema;异步审计 | -| **相关** | [`mq-abstraction.md`](./mq-abstraction.md) —— 异步审计通道(若采用)所用的 broker port | +| **相关** | dc3-common-mq 的 broker port(已落地)—— 异步审计通道(若采用)所用 | | **讨论** | 实施启动前开放评审 | ## 1. 摘要 diff --git a/docs/design/mq-abstraction.md b/docs/design/mq-abstraction.md deleted file mode 100644 index ec0c01091..000000000 --- a/docs/design/mq-abstraction.md +++ /dev/null @@ -1,142 +0,0 @@ -# IoT DC3 消息 Port 设计 - -| 项目 | 决策 | -|---|---| -| 状态 | 已落地;RabbitMQ、Kafka、Pulsar、MQTT 5 四个适配器均受 TCK 门禁 | -| 范围 | center、driver、common 之间的异步消息平面 | -| 目标 | 一个业务 Port、一个明确的 completion 契约、四个可验证实现 | -| 非目标 | broker 间兼容层、同步桥接、业务代码感知具体 broker | - -## 1. 第一性原理 - -消息消费的正确性不是“回调被调用”,而是“业务副作用完成后消息才被结算”。因此消费 API 的最小正确 -抽象是 `Publisher`:适配器订阅它,Publisher 完成前绝不 ACK、提交 offset 或发送 -PUBACK。所有实现都遵循同一状态机: - -```text -DELIVERED - ├─ Publisher complete -> ACK - ├─ MqPoisonException -> DEAD_LETTER - ├─ other error -> REQUEUE - ├─ cancellation/incomplete -> no settlement - └─ conflicting disposition -> adapter error, no settlement -``` - -业务 listener 必须返回 `Mono` 或 `Flux`,不得在 listener 内部调用 `subscribe()`,也不得取得 -broker 原生 acknowledgment。`Dc3ListenerProcessor` 是唯一的契约执行点,负责 MDC/request-id、异常分类和 -默认 `ACK`。 - -### 1.1 至少一次与幂等 - -Port 明确提供至少一次,不承诺 exactly-once。业务写库、发通知、执行设备命令等副作用必须以 tenant + -业务 delivery key 建立幂等约束。重投发生在以下窗口:进程崩溃、Publisher 取消、网络断开、Kafka 重新发布 -成功但原 offset 尚未提交。重复不是适配器 bug,而是至少一次模型的可观察结果。 - -### 1.2 batch 语义 - -批量 listener 接收 `List`,并对整批返回一个 disposition。默认完成才 ACK 整批;错误按 -统一规则重投或死信整批。适配器不得把批量拆成多个互相矛盾的结算结果。需要单条隔离时由业务显式拆分并 -设计幂等键,不能假设 broker 支持部分提交。 - -## 2. 模块边界 - -```text -dc3-mq-core - ├─ BrokerAdapter / BrokerCapabilities - ├─ MessageSender / ReactiveMessageSender - ├─ Dc3Listener / MqListener / Dc3ListenerProcessor - ├─ MqTopic / SubscriptionSpec / WireMqDelivery - └─ DeliveryDisposition / retry policy / batch properties - ▲ - ├─ dc3-mq-rabbitmq - ├─ dc3-mq-kafka - ├─ dc3-mq-pulsar - └─ dc3-mq-mqtt -``` - -业务模块只能引用 `dc3-mq-core`。适配器负责物理拓扑、客户端生命周期、序列化、重投和 broker 原生确认。 -启动时适配器发布 `BrokerCapabilities`,文档和测试只采用该声明,不根据理论能力推断行为。 - -## 3. 发送契约 - -`ReactiveMessageSender.sendConfirmed(WireMqMessage)` 返回 `Mono`,完成表示 broker 已确认发布;错误 -表示发布未被确认。发送方必须在业务完成链中组合该 Publisher,不得 fire-and-forget。结果发布、发件箱 -重发布和延迟调度均遵循同一确认语义。发送失败不能被转换为成功完成,否则消费方会永久丢失业务结果。 - -消息 envelope 使用 JSON body 与统一 headers: - -| Header | 用途 | -|---|---| -| `dc3-type` | payload 类型 | -| `request-id` | 跨 broker trace | -| `tenant-id` | 运维过滤;业务 tenant 仍在 payload/命令中显式传递 | -| `dc3-correlation-id` | 业务关联键 | -| `dc3-partition-key` | 无原生 key 字段时的镜像 | -| `dc3-redelivery-count` | 适配器重投计数 | - -## 4. 四个适配器的真实映射 - -| 能力 | RabbitMQ | Kafka | Pulsar | MQTT 5 | -|---|---|---|---|---| -| 任意延迟 | Port 本地调度 | Port 本地调度 | Port 本地调度 | Port 本地调度 | -| 死信 | 原生 DLX | Port 无原生队列 | Port 无原生队列 | Port 无原生队列 | -| 广播 | 原生交换机/队列 | topic + consumer group | subscription 模式 | `$share` 组 | -| 消费确认 | delivery tag | offset 提交 | message id | 手动 PUBACK | -| 发布确认 | publisher confirm | producer `acks=all` | send future | QoS 1 PUBACK | -| 批量 | 原生 consumer batch | poll batch | receive batch | 不支持 | -| 订阅过期 | queue expiry | 不支持 | 不支持 | 不支持 | -| 顺序 | 无 | 同一 key 分区内 | 无 | 无 | - -### 4.1 RabbitMQ - -Rabbit 适配器在 channel 上启用 publisher confirms 和 returns。业务 Publisher 完成后才确认 delivery -tag;重投使用 `basicNack(requeue=true)`,poison 分支使用死信发布成功后再拒绝原消息。连接、channel 和 -consumer 的关闭都取消未完成 Publisher,不产生 ACK。 - -### 4.2 Kafka - -Kafka 使用 `acks=all` producer 和手动 acknowledgment listener。Kafka 没有 per-message nack;`REQUEUE` -通过重新发布到同一逻辑 topic 后提交原 offset 实现,原子性边界由 broker 决定,因此业务必须幂等。消费组 -offset 持久存在,不提供订阅过期语义。TCK 使用真实 Apache Kafka 验证 Publisher completion、停止重投和 -重启窗口。 - -### 4.3 Pulsar - -Pulsar 使用 persistent topic、shared/exclusive subscription 和原生 message id ack。`REQUEUE` 使用 -negative acknowledgement,poison 使用适配器的死信发布路径。consumer close 会使未 ack 消息重新可见; -Publisher 未完成时不调用 ack/nack。 - -### 4.4 MQTT 5 - -MQTT 适配器使用 QoS 1、共享订阅和客户端手动 acknowledgment。只有 disposition 已完成且 ACK/死信动作成功 -后才发送 PUBACK;取消或异常不发送 PUBACK,依靠 broker 重投。协议没有原生批量、延迟、DLQ 或订阅过期, -这些能力不在 Port 中伪造。 - -## 5. 取消、失败和毒消息 - -- `Mono/Flux` cancellation 是未完成事务:适配器释放客户端资源,不结算消息。 -- transient failure 返回 `REQUEUE`;适配器按自身 broker 语义重投,并保留 redelivery count。 -- `MqPoisonException` 只用于不可恢复的 wire contract/schema 数据;死信发布失败时仍不得 ACK 原消息。 -- 同一 delivery 同时产生两个 disposition 是程序错误,抛出冲突异常并保持未结算。 -- adapter stop 必须等待或取消所有 in-flight completion;不能以 stop 作为隐式成功。 - -## 6. 配置与生命周期 - -`dc3.mq.type` 只激活一个适配器。所有 adapter bean 在 `start()` 建立消费者,在 `stop()` 取消订阅并释放 -客户端。连接错误通过 lifecycle Publisher 暴露,不能在后台线程吞掉。业务线程不调用 `block()`、 -`boundedElastic()` 或同步等待来“适配” broker 客户端。 - -## 7. TCK 发布门禁 - -`dc3-mq-tck` 对四个适配器执行同一套契约:发布确认、单条/批量消费、key 路由、广播/负载均衡、Publisher -completion、重投、poison、取消和生命周期。真实 broker 测试必须验证“消息已投递但 Publisher 未完成,停止 -consumer 后使用同 group 重建仍可重投”。任何适配器未通过完整 TCK,都不属于支持矩阵。 - -```bash -mvn -s .mvn/settings.xml -pl dc3-mq/dc3-mq-tck -am verify -``` - -## 8. 决策结果 - -删除无法满足上述 completion 契约的实现,而不是在 Port 中保留同步兼容分支。部署、接口和业务代码都以这套 -统一异步语义为准;不存在旧 API、旧 ACK 路径或 broker 专属业务分支。 diff --git a/docs/design/storage-abstraction.md b/docs/design/storage-abstraction.md deleted file mode 100644 index 3ad1040b3..000000000 --- a/docs/design/storage-abstraction.md +++ /dev/null @@ -1,82 +0,0 @@ -# 设计:统一 PostgreSQL R2DBC 存储 - -| 项目 | 决策 | -| --- | --- | -| 状态 | 已落地(flag day) | -| 范围 | Auth、Manager、Data、Agentic、运行时操作、审计和时序历史 | -| 关系库 | PostgreSQL 18 | -| 访问层 | Spring Boot R2DBC + `DatabaseClient` | -| 时序扩展 | TimescaleDB,仍位于同一 PostgreSQL 实例 | -| 分页 | 关系查询 offset;历史样本签名 cursor | -| 兼容策略 | 不保留 JDBC/MyBatis、旧 `R`、旧页码协议或双写适配层 | - -## 1. 第一性原理 - -平台的核心问题不是“支持多少数据库”,而是让租户数据在并发写入、取消、重试和跨服务调用下保持可证明的一致性。多套数据库方言会复制事务、锁、JSON、分页、种子和测试语义;收益只有在明确的部署需求存在时才成立。当前没有这种需求,因此选择一个能同时覆盖关系、时序、JSON 和向量能力的 PostgreSQL,并把其语义直接暴露在一个响应式存储边界内。 - -## 2. 运行时拓扑 - -```text -HTTP/WebFlux ─┐ -gRPC ├─> Reactive service ─> Repository port ─> DatabaseClient -Agentic/MCP ──┘ │ - └─> PostgreSQL 18 - ├─ dc3_* schemas - ├─ TimescaleDB hypertables - └─ pgvector / AGE capabilities -``` - -Spring Boot 只创建一个连接池和一个 `R2dbcTransactionManager`。服务通过 facade 访问其他域;控制器和业务代码不感知连接 URL、驱动或传输细节。 - -## 3. 数据格式 - -- ID 使用 UUIDv7 生成的 64 位逻辑标识,写入和 gRPC/HTTP 表示保持稳定。 -- 时间使用 UTC `Instant`,数据库统一 `TIMESTAMPTZ(6)`;禁止本地时区 `LocalDateTime` 作为跨服务协议。 -- 扩展字段使用规范化 JSONB(稳定键序、无未定义数字/时间类型);所有参数使用绑定变量。 -- 响应直接返回资源或 `items` 集合;错误使用 RFC 9457 `application/problem+json`,gRPC 使用标准 status code。 - -## 4. 分页与排序 - -### 4.1 关系列表 - -```json -{ - "offset": 0, - "limit": 50, - "sort": [{"field": "createTime", "direction": "DESC"}] -} -``` - -```json -{ - "items": [], - "offset": 0, - "limit": 50, - "total": 0, - "hasNext": false -} -``` - -`limit` 强制为 `1..200`;`offset + limit` 防止整数溢出。排序字段先过 endpoint 白名单,再映射为固定 SQL 列。count 与 items 在同一个 R2DBC 事务快照中顺序执行,不使用 `Mono.zip` 跨连接并发查询。 - -### 4.2 历史样本 - -历史点值按 `(device_time, series_id, message_id)` 生成签名不透明 cursor。请求只携带 `cursor` 和 `limit`,服务端验证租户、窗口和签名;不接受 `page`、`current`、`size`、`records` 或 SQL 列名。 - -## 5. 租户与并发 - -每个 tenant-scoped repository 方法都接收 `tenantId`,SQL 同时带租户谓词。跨表 join、correlation、series 聚合先限定租户再 join。软删除默认过滤;写入使用幂等键或唯一约束;更新/删除使用版本条件并检查 `rowsUpdated`。驱动租约采用 fencing token,过期 claim 使用行锁和单事务。 - -## 6. 响应式与取消 - -所有 repository/service/facade/controller 方法返回 `Mono`/`Flux`。R2DBC publisher 的取消信号释放连接、终止下游 gRPC/SSE,并把 Agentic 消息状态落为 `CANCELLED`;禁止 `.block()`、`Schedulers.boundedElastic()` 和“同步客户端外包 Reactor”。长任务返回 `202 OperationAccepted`,状态由 `dc3_operation` 查询或 watch。 - -## 7. 测试门禁 - -- `check_r2dbc_migration.py`:MyBatis/JDBC/旧分页/旧响应/阻塞桥接均为零。 -- PostgreSQL schema fingerprint:种子脚本、运行时表和 JSON/时间格式变更必须同步更新指纹。 -- `dc3-db-tck`:真实 PostgreSQL 容器验证租户隔离、软删除、乐观锁、幂等、fencing、分页和取消边界。 -- Web/CLI contract:只发送 `offset`、`limit`、`sort` 或 `cursor`,只读取 `items`、`total`、`hasNext`/`nextCursor`;删除统一 HTTP `DELETE`。 -- gRPC contract:普通 CRUD unary,真实流才 server-streaming;错误使用 `NOT_FOUND`、`INVALID_ARGUMENT`、`PERMISSION_DENIED`、`ALREADY_EXISTS` 等标准状态。 - -任何门禁失败都阻止发布。该设计不提供兼容层或渐进双写;需要其他数据库时必须重新评审并建立独立契约,而不是在当前运行时偷偷增加分支。 diff --git a/docs/design/tsdb-abstraction.md b/docs/design/tsdb-abstraction.md deleted file mode 100644 index c5a7efa25..000000000 --- a/docs/design/tsdb-abstraction.md +++ /dev/null @@ -1,157 +0,0 @@ -# TSDB 抽象(当前架构) - -> 状态:已落地。本文描述源码中的唯一实现,不保留历史适配器方案。 - -## 1. 第一性原理 - -平台要解决的是三件事:可靠接收设备样本、按租户和序列读取历史、提供可预测的聚合查询。数据库不是 -业务边界;业务边界是可取消、可超时、可验证的响应式 Port。因此: - -- 数据面只选择一个技术栈:PostgreSQL/TimescaleDB + R2DBC; -- 传输和存储都使用 Reactor,取消必须能到达数据库请求; -- 错误通过异常/transport status 表达,不把错误塞入成功 payload; -- 不做兼容层、双写和运行时多库分支,避免语义漂移和测试矩阵爆炸。 - -## 2. 组件关系 - -```text -driver -> data ingest service -> R2dbcTsdbStore -> PostgreSQL/TimescaleDB - -> dc3_point_value_latest (relational projection) -gateway -> data facade -> PointValueService -> TsdbStore -``` - -`dc3-tsdb-core` 仅包含模型和 Port。`dc3-common-data` 是实现和唯一生产消费者;其他中心通过 facade -和 gRPC/HTTP 契约访问,不直接接触 `DatabaseClient`。 - -## 3. 领域模型 - -```java -record SeriesKey(long tenantId, long deviceId, long pointId) {} -record SeriesFilter(long tenantId, List series) {} -record PointValueSample( - SeriesKey series, - Instant deviceTime, - Instant receiveTime, - String rawValue, - String calValue, - Double numericValue, - int quality, - String messageId, - int schemaVersion, - String driverNode, - long sequence, - long fencingToken, - long driverId) {} -``` - -`SeriesFilter` 的 `tenantId` 永远存在;`series` 为空仅表示已授权的租户级扫描。序列 ID 和租户 ID 必须 -为正数,所有构造路径都执行校验。样本时间使用 UTC 微秒 `Instant`;原始值和数值投影同时保存,禁止 -用默认值掩盖解析失败。 - -## 4. 响应式 Port - -`io.github.pnoker.common.tsdb.spi.TsdbStore` 定义: - -```java -Mono append(List samples); -Mono>> last( - SeriesFilter filter, int limit, TsdbDeadline deadline); -Mono> history( - SeriesFilter filter, TimeWindow window, Cursor cursor, - int pageSize, TsdbDeadline deadline); -Mono> aggregate( - SeriesFilter filter, AggregateFunction fn, TimeWindow window, - Double percentile, TsdbDeadline deadline); -Mono>> bucketedAggregate( - SeriesFilter filter, AggregateFunction fn, TimeWindow window, - Duration bucketWidth, Double percentile, TsdbDeadline deadline); -Mono count(SeriesFilter filter, TimeWindow window, TsdbDeadline deadline); -``` - -分析操作还包括 `seriesCounts`、`seriesLastSeen`、`latencyHistogram` 和 `correlation`。每个操作都返回 -`Mono` 或 `Flux`;Port 不暴露 `Future.get`、JDBC `ResultSet` 或阻塞迭代器。 - -## 5. R2DBC 实现 - -`R2dbcTsdbStore` 使用 `DatabaseClient` 绑定参数执行 SQL,使用已注入的 `R2dbcDialect` 处理时间和方言 -差异,使用 `TransactionalOperator` 包围需要原子性的写入和读快照。表固定为 -`dc3_history.dc3_point_value`,实现不读取环境变量来选择其他存储。 - -### 写入不变量 - -1. `samples` 不得为 `null`、空或含 `null`,数量不得超过 `capabilities.maxAppendBatch`。 -2. 每个样本的租户、设备、点、driver identity、fencing token 和时间都经过正数/范围校验。 -3. Upsert 使用 `ON CONFLICT`,绑定全部参数,不拼接用户输入。 -4. 返回接受数量;任意行失败时整批失败,不返回部分成功。 - -### 读取不变量 - -- `tenant_id` 是所有 WHERE 和 join 的第一过滤条件;series 集合必须与 filter 的租户一致; -- `last`、`history` 和 count 上限硬失败,不自动 clamp; -- cursor 是 `(deviceTime, series, messageId)` 的稳定锚点,使用严格小于比较支持降序翻页; -- bucket width、percentile、deadline 必须为有限正值; -- sort、聚合函数和表名均来自枚举/白名单,不接收任意 SQL 片段; -- 每个查询挂接 deadline,取消时释放 R2DBC publisher 和事务。 - -## 6. 分页契约 - -TSDB 历史查询使用 cursor: - -```json -{ - "items": [ ... ], - "nextCursor": "opaque-base64" -} -``` - -管理实体查询使用偏移分页: - -```json -{ - "items": [ ... ], - "offset": 0, - "limit": 50, - "total": 123, - "hasNext": true -} -``` - -两者不能混用;客户端不得发送 `page/current/pages/records`。offset、limit 和 sort 只在关系查询边界 -出现,历史大窗口不得通过不断增大 offset 模拟 cursor。 - -## 7. 一致性与租户隔离 - -最新值投影、历史写入和幂等 outbox 在 Data 域内按明确事务边界处理。跨服务调用只能经 facade;gRPC -请求携带 tenant ID,服务端再次校验资源归属。缓存键必须包含 tenant ID、series identity 和 schema -version。任何 `tenant_id IS NULL` 分支都视为缺陷,除非数据模型明确声明全局记录。 - -## 8. 错误、取消与超时 - -业务错误在服务层抛出 `NotFoundException`、`RequestException` 等,由 gRPC server 映射为 -`NOT_FOUND`、`INVALID_ARGUMENT`、`FAILED_PRECONDITION` 或 `INTERNAL`。成功响应不含应用层错误信封。 -客户端将 transport status 转为 Reactor error 或空结果(仅对明确的 not-found 查询)。 - -HTTP 流式接口将断开映射为 Reactor cancel;MCP/agentic 流程在取消时停止工具调用并写入最终状态,不能 -把取消误记为成功。所有跨服务请求设置 deadline,重试仅允许在幂等操作且未观察到副作用时执行。 - -## 9. 能力声明 - -R2DBC 实现声明微秒精度、租户级扫描、分桶聚合和相关性分析能力;rollup 为 `NONE`,不会伪装成已物化 -聚合。能力由 `TsdbCapabilities` 暴露,调用方在不支持时明确拒绝或选择正确的原始扫描路径,不能静默 -返回近似数据。 - -## 10. 测试门槛 - -- `dc3-tsdb-core`:模型、cursor 比较、percentile 和 deadline 参数契约; -- `dc3-common-data`:每个 R2DBC store 的租户、软删除、乐观锁、冲突、空输入和取消负向测试; -- integration:真实 PostgreSQL 下验证事务原子性、COUNT/items 快照一致性、微秒边界和跨租户拒绝; -- gRPC/HTTP contract:验证直接 payload、标准 status、offset 分页字段和 cursor 不透明性; -- 前端/CLI:验证空页、最后一页、total 变化、流取消及不再发送旧字段。 - -```bash -mvn -s .mvn/settings.xml -q -DskipTests compile -mvn -s .mvn/settings.xml -q test \ - -pl dc3-tsdb/dc3-tsdb-core,dc3-common/dc3-common-data -am -``` - -新增数据库后必须先提供真实异步驱动和完整契约测试;在此之前不添加第二个适配器或兼容 API。