mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-10-22 13:12:49 +00:00
docs(protocol): require facade boundary in runtime spec
This commit is contained in:
1 parent
4a15a787ff
commit
de1b1eb080
1 file changed
+202
-21
@@ -48,6 +48,32 @@ Status date: 2026-06-14
|
||||
|
||||
本文只采用少数 GoF 23 设计模式作为主轴,避免“模式堆砌”。
|
||||
|
||||
### Design Verdict
|
||||
|
||||
本规格接受原始改造设想作为约束基线:Trail Mate 的协议层改造不是把若干 helper
|
||||
抽到 shared 目录,而是建立一条清晰的用例入口到平台执行链路:
|
||||
|
||||
```text
|
||||
UI / ChatService
|
||||
-> MeshProtocolFacade
|
||||
-> ProtocolIntent
|
||||
-> IProtocolRuntime strategy
|
||||
-> Protocol State
|
||||
-> ProtocolEffects
|
||||
-> IProtocolEffectExecutor bridge
|
||||
-> Platform adapters
|
||||
```
|
||||
|
||||
其中 `Strategy + Command + State + Bridge + Adapter` 是主轴。如果这五个没有立住,
|
||||
`Visitor / Chain of Responsibility / Facade / Factory / Builder` 都只能算局部工具,不能宣称
|
||||
协议 runtime 改造完成。
|
||||
|
||||
`Facade` 虽然不是前五个主轴之一,但它是上层用例入口边界,不能只存在于图里。规格中出现
|
||||
`MeshProtocolFacade` 时,表示必须有代码级对象或等价命名的 concrete boundary。该对象负责给
|
||||
UI / ChatService 暴露稳定用例 API,并隐藏 runtime 选择、effect 执行、TX 失败回灌和 action
|
||||
result 投影的编排细节。若未来决定不用 `MeshProtocolFacade` 这个名字,必须先修改本规格,
|
||||
明确新的等价代码对象和验收条件。
|
||||
|
||||
### Language Baseline
|
||||
|
||||
目标方向是现代 C++,长期可以提升到 C++20;但当前 ESP32 / nRF52 的 PlatformIO
|
||||
@@ -77,8 +103,8 @@ C++20,并考虑引入 concepts / `std::span` / 更强类型约束。
|
||||
| --- | --- | --- |
|
||||
| Visitor | `std::variant<ProtocolEffect...>` + `std::visit` 的 effect dispatch | 只用于 executor/test 处理 effects,不扩散到 UI |
|
||||
| Chain of Responsibility | incoming packet 分类管线:decrypt -> routing -> nodeinfo -> position -> trace -> text/appdata | handler 顺序必须写进 runtime 或 spec,不能形成隐式吞包黑洞 |
|
||||
| Facade | `MeshProtocolFacade` 给 UI/ChatService 的入口 | 只做用例门面,不沉淀协议语义 |
|
||||
| Abstract Factory / Factory Method | product composition 选择 runtime + executor + codec | 用静态对象/引用也可以,不强制动态分配 |
|
||||
| Facade | `MeshProtocolFacade` 给 UI/ChatService 的入口 | 必须落成真实代码边界;只做用例门面,不沉淀协议语义 |
|
||||
| Abstract Factory / Factory Method | product composition 选择 facade + runtime + executor + codec | 用静态对象/引用也可以,不强制动态分配 |
|
||||
| Builder | packet/control payload 构造 | 局部用于 codec/build request,不能替代 runtime |
|
||||
|
||||
### Patterns To Avoid As Main Axis
|
||||
@@ -118,6 +144,42 @@ Runtime 是协议真相所在:
|
||||
- `MeshCoreRuntime` 解释 MeshCore Intent、incoming frame、trace path、NodeInfo control、tick;
|
||||
- runtime 可以拥有 State,但不能拥有平台 IO。
|
||||
|
||||
### Facade
|
||||
|
||||
Facade 是 UI / ChatService 面对协议系统的稳定用例入口。它不是 protocol runtime 的别名,也不是
|
||||
adapter 的包装名。
|
||||
|
||||
`MeshProtocolFacade` 必须拥有或聚合:
|
||||
|
||||
- 当前协议的 `IProtocolRuntime` strategy;
|
||||
- 对应平台的 `IProtocolEffectExecutor` bridge;
|
||||
- 构造 `RuntimeContext` 所需的 clock / self node / protocol facts provider;
|
||||
- 将 `ProtocolEffects` 执行、记录、回灌 `TxResult`、并向上层返回 app-facing result 的编排逻辑。
|
||||
|
||||
Facade 必须暴露的是用例动作,而不是 wire 细节:
|
||||
|
||||
- `startTraceRoute(peer)`
|
||||
- `exchangePosition(peer)`
|
||||
- `requestNodeInfo(peer, wantResponse)`
|
||||
- `sendText(channel, peer, text)`
|
||||
- `sharePosition(...)`
|
||||
- `shareWaypoint(...)`
|
||||
- `handleIncoming(packet)`
|
||||
- `handleTxResult(result)`
|
||||
- `tick(nowMs)`
|
||||
|
||||
Facade 不得:
|
||||
|
||||
- 直接选择 Meshtastic portnum 或 MeshCore payload type;
|
||||
- 编码 protobuf / MeshCore frame;
|
||||
- 决定 PKI resync、NodeInfo reply、TraceRoute completion、ACK timeout 等协议语义;
|
||||
- 执行 radio/storage/BLE 细节;
|
||||
- 变成新的 God Object 或 Mediator。
|
||||
|
||||
Facade 的职责是隔离上层与协议编排。上层不应该直接拼 `ProtocolIntent` 后调用 runtime,
|
||||
也不应该直接遍历 `ProtocolEffects` 后调用 executor。过渡期允许少量平台 adapter 仍然这样做,
|
||||
但每个 active UI / ChatService 入口必须有迁入 `MeshProtocolFacade` 的计划和测试。
|
||||
|
||||
### Effect
|
||||
|
||||
Effect 是 runtime 要求外部世界发生的动作:
|
||||
@@ -154,6 +216,64 @@ Codec 只负责编解码:
|
||||
- `MeshCoreCodec`:frame/header/direct data/group data/control/trace;
|
||||
- codec 可以使用 Builder 风格的 build request,但不得保存业务状态。
|
||||
|
||||
### Product Composition / Factory
|
||||
|
||||
产品组合阶段负责把 `MeshProtocolFacade`、`IProtocolRuntime`、`IProtocolEffectExecutor`、codec 和平台
|
||||
facts provider 接起来。这里使用 Abstract Factory / Factory Method 的意图是避免每个 UI 或 adapter
|
||||
手动拼装协议对象。
|
||||
|
||||
嵌入式平台不要求动态分配。允许使用静态对象和返回引用的工厂:
|
||||
|
||||
```cpp
|
||||
struct ProtocolRuntimeBundle
|
||||
{
|
||||
MeshProtocolFacade* facade;
|
||||
IProtocolRuntime* runtime;
|
||||
IProtocolEffectExecutor* executor;
|
||||
};
|
||||
|
||||
ProtocolRuntimeBundle protocolRuntimeFor(MeshProtocol protocol);
|
||||
```
|
||||
|
||||
Factory 可以知道产品/平台能力,但不得决定协议语义。它选择“用哪套 runtime/executor”,不决定
|
||||
“该不该回复 NodeInfo”或“TraceRoute 何时完成”。
|
||||
|
||||
### Incoming Handler Chain
|
||||
|
||||
Incoming packet 的分类可以局部使用 Chain of Responsibility,但顺序必须显式,不能形成隐式吞包。
|
||||
handler 可以解密、分类、更新状态、产生 effects,但必须使用统一返回语义:
|
||||
|
||||
```cpp
|
||||
enum class PacketHandling
|
||||
{
|
||||
NotHandled,
|
||||
HandledContinue,
|
||||
HandledStop,
|
||||
DropWithEffects,
|
||||
};
|
||||
```
|
||||
|
||||
Meshtastic incoming chain 的概念顺序必须是:
|
||||
|
||||
1. wire/Data 解码与 channel / PKI 解密事实建立;
|
||||
2. routing app / routing error;
|
||||
3. NodeInfo;
|
||||
4. Position;
|
||||
5. TraceRoute;
|
||||
6. KeyVerification;
|
||||
7. text / generic app-data projection。
|
||||
|
||||
MeshCore incoming chain 的概念顺序必须是:
|
||||
|
||||
1. frame/header/direct/group 解码与 peer identity facts 建立;
|
||||
2. ACK;
|
||||
3. control: discover / NodeInfo;
|
||||
4. trace;
|
||||
5. text / app-data projection。
|
||||
|
||||
这些 handler 可以先以函数存在,也可以之后拆成对象;但顺序和吞包语义必须属于 runtime/codec
|
||||
边界,不能散落在平台 adapter 的任意 if 分支里。
|
||||
|
||||
## Primary Class Model
|
||||
|
||||
```mermaid
|
||||
@@ -163,8 +283,11 @@ classDiagram
|
||||
+exchangePosition(NodeId peer)
|
||||
+requestNodeInfo(NodeId peer, bool wantResponse)
|
||||
+sendText(ChannelId channel, NodeId peer, string text)
|
||||
+onIncomingBytes(bytes)
|
||||
+tick(nowMs)
|
||||
+sharePosition(PositionSnapshot position)
|
||||
+shareWaypoint(WaypointSnapshot waypoint)
|
||||
+handleIncoming(IncomingPacket packet)
|
||||
+handleTxResult(TxResult result)
|
||||
+tick(uint32_t nowMs)
|
||||
}
|
||||
|
||||
class IProtocolRuntime {
|
||||
@@ -204,9 +327,16 @@ classDiagram
|
||||
class NrfProtocolExecutor
|
||||
class RecordingProtocolExecutor
|
||||
|
||||
class ProtocolRuntimeFactory {
|
||||
+protocolRuntimeFor(MeshProtocol protocol) ProtocolRuntimeBundle
|
||||
}
|
||||
|
||||
class MeshtasticCodec
|
||||
class MeshCoreCodec
|
||||
|
||||
ProtocolRuntimeFactory ..> MeshProtocolFacade
|
||||
ProtocolRuntimeFactory ..> IProtocolRuntime
|
||||
ProtocolRuntimeFactory ..> IProtocolEffectExecutor
|
||||
MeshProtocolFacade o-- IProtocolRuntime
|
||||
MeshProtocolFacade o-- IProtocolEffectExecutor
|
||||
IProtocolRuntime <|.. MeshtasticRuntime
|
||||
@@ -225,8 +355,11 @@ classDiagram
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI[UI / ChatService] -->|Command: ProtocolIntent| Facade[MeshProtocolFacade]
|
||||
Facade -->|Strategy dispatch| Runtime[IProtocolRuntime]
|
||||
UI[UI / ChatService] -->|Use-case command| Facade[MeshProtocolFacade]
|
||||
PlatformIn[Platform packet source] -->|IncomingPacket / TxResult / tick| Facade
|
||||
Facade -->|build ProtocolIntent| Intent[ProtocolIntent]
|
||||
Intent -->|Strategy dispatch| Runtime[IProtocolRuntime]
|
||||
Facade -->|direct incoming / tx / tick| Runtime
|
||||
Runtime --> MT[MeshtasticRuntime]
|
||||
Runtime --> MC[MeshCoreRuntime]
|
||||
MT -->|State| MTState[ACK / TraceRoute / Position / PKI states]
|
||||
@@ -235,7 +368,8 @@ flowchart LR
|
||||
MC -->|Codec dependency| MCCodec[MeshCoreCodec]
|
||||
MT -->|Effects| Effects[ProtocolEffects]
|
||||
MC -->|Effects| Effects
|
||||
Effects -->|Visitor dispatch| Executor[IProtocolEffectExecutor]
|
||||
Effects -->|returned to Facade| Facade
|
||||
Facade -->|Visitor dispatch| Executor[IProtocolEffectExecutor]
|
||||
Executor -->|Bridge implementation| Esp[ESP executor]
|
||||
Executor -->|Bridge implementation| Nrf[nRF executor]
|
||||
Esp -->|Adapter| EspIO[ESP radio / storage / BLE]
|
||||
@@ -258,6 +392,7 @@ sequenceDiagram
|
||||
participant Platform as Platform IO
|
||||
|
||||
User->>Facade: startTraceRoute(peer)
|
||||
Facade->>Facade: build TraceRouteIntent + RuntimeContext
|
||||
Facade->>Runtime: prepareOutgoing(TraceRouteIntent)
|
||||
Runtime->>State: allocate request id and enter pending state
|
||||
Runtime-->>Facade: ProtocolEffects(SendPacket/SendTraceRoute)
|
||||
@@ -267,7 +402,13 @@ sequenceDiagram
|
||||
Codec-->>Executor: EncodedPacket
|
||||
Executor->>Platform: radio/storage/event operation
|
||||
Platform-->>Executor: ok/fail
|
||||
Executor-->>Facade: EffectExecutionResult
|
||||
end
|
||||
opt tx failure / tx metadata
|
||||
Facade->>Runtime: handleTxResult(TxResult)
|
||||
Runtime-->>Facade: ProtocolEffects(EmitActionResultEffect...)
|
||||
end
|
||||
Facade-->>User: ActionHandle / app-facing result
|
||||
```
|
||||
|
||||
### Incoming Packet
|
||||
@@ -276,15 +417,17 @@ sequenceDiagram
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Platform as Platform IO
|
||||
participant Executor as Platform Adapter
|
||||
participant Adapter as Platform Adapter
|
||||
participant Facade as MeshProtocolFacade
|
||||
participant Runtime as IProtocolRuntime
|
||||
participant Chain as Incoming Handler Chain
|
||||
participant State as Protocol State
|
||||
participant Facade as MeshProtocolFacade
|
||||
participant UI as UI/ChatService
|
||||
|
||||
Platform->>Executor: raw packet/frame
|
||||
Executor->>Runtime: handleIncoming(IncomingPacket)
|
||||
Platform->>Adapter: raw packet/frame
|
||||
Adapter->>Adapter: decode/decrypt enough to form IncomingPacket facts
|
||||
Adapter->>Facade: handleIncoming(IncomingPacket)
|
||||
Facade->>Runtime: handleIncoming(IncomingPacket)
|
||||
Runtime->>Chain: decrypt/classify/route handlers
|
||||
alt NodeInfo request
|
||||
Chain->>State: apply NodeInfo rule
|
||||
@@ -296,8 +439,8 @@ sequenceDiagram
|
||||
Chain->>State: complete trace action
|
||||
State-->>Runtime: EmitActionResultEffect
|
||||
end
|
||||
Runtime-->>Executor: ProtocolEffects
|
||||
Executor-->>Facade: publish text/data/action result effects
|
||||
Runtime-->>Facade: ProtocolEffects
|
||||
Facade->>Adapter: execute platform effects through executor
|
||||
Facade-->>UI: stable app-facing result
|
||||
```
|
||||
|
||||
@@ -479,19 +622,55 @@ This split intentionally leaves direct ACK burst scheduling and multi-ACK frame
|
||||
radio execution details. The question "is this local send still pending, completed, failed, or timed out" belongs to
|
||||
`MeshCoreRuntime`.
|
||||
|
||||
## Spec Conformance Criteria
|
||||
|
||||
不能只因为 runtime helper 被抽出来就宣称改造完成。满足本规格必须同时满足以下条件:
|
||||
|
||||
1. `IProtocolRuntime`、`MeshtasticRuntime`、`MeshCoreRuntime` 是真实代码对象,并有 shared tests 覆盖
|
||||
outgoing / incoming / tx result / tick。
|
||||
2. `ProtocolIntent` 是 UI / usecase 进入协议系统的命令模型;active UI / ChatService 入口不得直接制造
|
||||
portnum、payload type、protobuf bytes 或 MeshCore control frame。
|
||||
3. 跨时间协议会话必须由 runtime state 拥有。UI 和 adapter 不得独立维护 TraceRoute / Position
|
||||
exchange / PKI resync / ACK completion 的状态转移。
|
||||
4. `IProtocolEffectExecutor` 或等价 executor bridge 是真实代码对象;平台只执行 `ProtocolEffect`,不决定
|
||||
协议语义。
|
||||
5. `MeshProtocolFacade` 或明确命名的等价 facade 是真实代码对象。它必须是 UI / ChatService 面向协议系统的
|
||||
稳定用例入口,负责选择 runtime、构造 intent、执行 effects、回灌 tx result、返回 app-facing result。
|
||||
6. Product factory / composition 不能散落在 UI 中。平台可以使用静态对象,但对象选择必须集中在产品组合层。
|
||||
7. Incoming handler chain 的顺序和吞包语义必须写进 runtime/codec 边界,不能只靠平台 adapter 巨型 if
|
||||
分支维持。
|
||||
8. 如果规格中的核心类名只存在于 Mermaid 图中,而代码里没有真实对象或等价声明,则该项未完成。
|
||||
|
||||
## Current Implementation Gaps
|
||||
|
||||
截至本规格日期,runtime / effect 主干已经部分落地,但以下缺口仍然阻止本规格被判定为完成:
|
||||
|
||||
1. `MeshProtocolFacade` 还没有真实代码对象。当前若干 active UI / platform path 仍直接构造
|
||||
`ProtocolIntent`、调用 `IProtocolRuntime`、遍历 `ProtocolEffects` 或手动处理 TX failure 回灌。
|
||||
2. Product composition 还没有统一的 factory / bundle 边界。平台代码各自持有 runtime / executor,
|
||||
组合逻辑仍可能继续散落。
|
||||
3. Incoming handler chain 尚未以 `PacketHandling` 或等价返回语义显式化。部分平台 adapter 仍保留
|
||||
巨型 decode/classify 分支,虽然其中若干协议决策已经下沉到 runtime/policy。
|
||||
4. `RecordingProtocolExecutor` 已覆盖 effect 可记录性,但还缺少 `MeshProtocolFacade` 级别的 smoke,
|
||||
用来证明 UI / ChatService 可以只通过 facade 发起 action 并观察 app-facing result。
|
||||
|
||||
## Migration Rules
|
||||
|
||||
1. Add shared contracts first: Intent, Effect, Runtime, Executor.
|
||||
2. Add test-only `RecordingProtocolExecutor` to prove effects are inspectable.
|
||||
3. Move one state machine at a time:
|
||||
2. Add a real Facade boundary before claiming UI / ChatService decoupling:
|
||||
- first create `MeshProtocolFacade` or update this spec with the exact equivalent code object;
|
||||
- facade must hide runtime selection and effect execution from active UI / ChatService paths;
|
||||
- add smoke tests that fail if the facade object disappears.
|
||||
3. Add test-only `RecordingProtocolExecutor` to prove effects are inspectable.
|
||||
4. Move one state machine at a time:
|
||||
- Meshtastic PKI resync;
|
||||
- Meshtastic TraceRoute/Position action lifecycle;
|
||||
- Meshtastic NodeInfo/Position reply packet construction;
|
||||
- MeshCore NodeInfo control payload build/parse;
|
||||
- MeshCore trace action lifecycle.
|
||||
4. Replace adapter logic with `executeEffect(...)` calls only after the shared state test exists.
|
||||
5. Update `MeshCapabilities` when a protocol action becomes truly shared and executable.
|
||||
6. Keep existing helper functions only as codec/building utilities, not as orchestration owners.
|
||||
5. Replace adapter logic with `executeEffect(...)` calls only after the shared state test exists.
|
||||
6. Update `MeshCapabilities` when a protocol action becomes truly shared and executable.
|
||||
7. Keep existing helper functions only as codec/building utilities, not as orchestration owners.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
@@ -501,9 +680,11 @@ Before changing protocol code:
|
||||
2. Name the GoF pattern involved and why it is being used.
|
||||
3. If changing a platform adapter, verify the change is only executor/adapter work.
|
||||
4. If changing protocol semantics, put the rule in shared runtime and add a shared test.
|
||||
5. Update this spec when the pattern boundary changes.
|
||||
6. Update `PROTOCOL_ADAPTER_DRIFT_AUDIT.md` when a drift item is resolved or accepted.
|
||||
7. Run GitNexus impact analysis before edits and `detect-changes` before commit.
|
||||
5. If the spec names a core class or boundary, verify the code contains that object or an explicitly documented
|
||||
equivalent. Mermaid-only architecture does not count as implementation.
|
||||
6. Update this spec when the pattern boundary changes.
|
||||
7. Update `PROTOCOL_ADAPTER_DRIFT_AUDIT.md` when a drift item is resolved or accepted.
|
||||
8. Run GitNexus impact analysis before edits and `detect-changes` before commit.
|
||||
|
||||
## Relationship To Other Specs
|
||||
|
||||
|
||||
Reference in new issue
Block a user