# Structive 设计理念与原则 [English](DESIGN.md) ## 1. 设计目标 Structive 的目标是给普通 C++ 对象增加一层机器可理解的结构语义,同时不强迫业务对象改用框架自己的存储模型。 它应该能够回答: - 哪些成员属于公开结构模型? - 某个属性稳定的 runtime key 是什么? - 哪些属性允许外部读取或写入? - 哪些属性参与持久化? - 哪些元数据属于 Presentation、Validation 或其他扩展? - 哪些属性位于同一个同步域? - 动态适配器如何在遵守 capability 的前提下读写属性? 但完成这些事情以后,底层类型仍然应该看起来像正常的 C++。 ## 2. 第一原则:增强,而不是替代 Structive 不是第二套对象模型,而是附加在已有 C++ 类型旁边的结构层。 推荐形态: ```cpp struct Device : structive::Property_Object { double temperature; std::string name; }; ``` 而不是: ```cpp struct Device { Framework_Property temperature; Framework_Property name; }; ``` 这个原则保护了原生 C++ 的几个关键性质: 1. 成员仍然是真实成员。 2. 成员指针仍然可以作为可靠身份。 3. 所有权规则允许时仍然可以 raw access。 4. 未注册成员可以继续作为实现细节存在。 5. Structive 元数据可以和存储形式独立演进。 这个原则也有明确代价:Structive 不可能拦截直接成员访问。只有通过 managed path 的访问才会获得 Structive 管理行为。 ## 3. 必须保持两层,而不是揉成一层 Structive 把类型级描述和实例级管理分开。 ### 3.1 类型层 `Type_Descriptor` 产生 `Object_Schema`,描述: - 注册属性; - 属性 key; - accessor; - Attribute; - constraint; - object 级可继承默认值; - 默认 synchronization plan。 这是类型的结构定义。 ### 3.2 实例层 `Property_Object` 提供: - 同一类型共享的默认解析 lock slot 拓扑; - 只有真实锁策略才需要的每实例 mutex 存储; - 只有显式同步覆盖实例才持有的紧凑 topology; - managed typed read/write; - capability view; - 静态与运行时多属性 guard; - managed value traversal; - `Property_Object_Base` type-erased runtime access。 这是实例行为,不是 Schema 身份。 ### 3.3 设计约束 不要把可变的实例同步状态塞进 Schema,也不要让 Schema 必须依赖某一种特定的实例管理策略。由类型级默认同步计划推导出的不可变 topology 可以由同类型所有实例共享。 以后完全可能有用户只想描述大量普通对象,却不愿意为每个对象承担同步状态成本。当前架构应该持续保留这种可能性。 ## 4. 注册必须显式 Structive 不认为所有 C++ 成员都天然属于结构模型。 ```cpp struct Device : structive::Property_Object { int id; double temperature; mutable int internal_cache; }; ``` 如果 Schema 只注册 `id` 和 `temperature`,那么 `internal_cache` 对 Structive 完全不可见。 这是故意的。结构暴露本身就是 API 设计,不应该根据物理布局自动推断。 ### 原则 **Schema 才是公开结构契约;struct 的物理成员集合不是自动 Schema。** ## 5. C++ 内部优先使用编译期身份 业务 C++ 代码通常应该通过成员指针定位 member-backed property: ```cpp device.read<&Device::temperature>(); device.write<&Device::temperature>(30.0); device.schema().property<&Device::temperature>(); ``` 这样编译器能够建立最强的“对象类型—字段”关系。 数字 index 适合编译期泛型遍历;字符串 key 适合运行时边界。 ### 身份层级 ```text 业务 C++ 代码 → member pointer 编译期泛型代码 → property index runtime / adapter → declared string key ``` 不要因为存在 key,就把本来可以静态确定的 C++ 业务代码全部字符串化。 ## 6. Key 是结构协议标识,不是显示名称 每个 `Property_Descriptor` 都必须拥有非空 `key`,同一 Schema 内 key 必须唯一。 Key 被 Schema lookup、动态锁选择、runtime read/write 使用。因此它不只是 UI label。 即使 C++ 成员名不变,修改 key 也可能改变外部协议或持久化契约。 ### 原则 **把 property key 当成协议级名字,重命名必须有意识。** ## 7. 只有一套 Attribute 系统 Structive 不应该再长出 `hint`、`annotation`、`ui metadata`、`serializer metadata` 等彼此独立的元数据容器。 一个 Attribute 可以声明: - `attribute_category`:用于按 category 查询; - `single_valued`:同一个声明中该 category 是否只能有一个值; - `inheritable`:是否允许进入 object defaults; - category 自己需要的 payload。 Core 和 Extension 使用完全相同的协议。 ### 原则 **增加语义时优先增加 Attribute category + interpreter,而不是再造一套 metadata framework。** ## 8. Category 必须有明确所有者 Core 只解释属于 Core 的 category。 例如 External Access 和 Persistence Access 会直接影响 Core capability view,所以 Core 理解它们是合理的;但 Core 不需要理解 `presentation::label`。 Presentation 解释 Presentation;未来 JSON 扩展解释 JSON;RPC 扩展解释 RPC。 Core 只负责统一保存、遍历和提供查询机制。 ### 依赖约束 ```text Extension implementation ↓ Structive Property Core ``` 不能为了某个扩展写起来方便,就反过来让 Core 依赖 Extension。 ## 9. Defaults 是继承,不是隐藏行为 `defaults(...)` 用于支持可继承 Attribute。Property 级声明仍然可以覆盖同 category 的默认值。 当前 Core 中可继承的 category 包括 External Access、Persistence Access 和 Sensitive。 Defaults 应该用于稳定的对象级政策默认值,而不是用来制造大量隐式行为。 ### 原则 **Defaults 用来减少重复,但不能让一个属性最终生效的政策变得无法从 Schema 规则中推导。** ## 10. Managed Access 与 Raw Access 是两种契约 Raw access: ```cpp device.temperature = 30.0; ``` Managed access: ```cpp device.write<&Device::temperature>(30.0); ``` Raw path 就是普通 C++,不会自动获得 Structive 锁和 capability 管理。 Managed path 会经过 descriptor 和该实例的同步拓扑。 两条路径同时存在是设计结果,不是漏洞。 ### 原则 不要假装继承 `Property_Object` 以后 public member 就自动变成强封装属性。如果某个子系统要求受管理同步,那么该子系统自己的编码约束必须要求使用 managed path。 ## 11. 固有能力先于边界投影 每个 Property 首先拥有 accessor 自身决定的能力: ```text intrinsic ├── read └── write ``` 应用内部的普通 managed code 通过 `read()`、`write()`、lock 和 traversal 直接使用这套固有能力,因此 `internal` 不再是单独的 capability mode。 External 和 Persistence 是同一份 Property definition 之上的边界投影: ```text Property ├── intrinsic: read / write ├── external: read / write projection └── persistence: load / store projection ``` Core 会把投影 Attribute 与 accessor 的实际能力结合起来。投影可以收窄固有能力,但绝不能凭空创造 accessor 本身不具备的能力。 External view 不应该发展成第二份 Schema;Persistence view 也不应该成为第二份 Schema。它们都只是同一 Schema 的能力投影。 ### 原则 **一份 property definition,固有能力明确,边界投影显式。** ## 12. Validation = 元数据 + 显式操作 Constraint 描述候选值是否合法,但 `write()` 不会自动执行它。 必须保持分开的概念包括: ```text 单字段 constraint 跨字段 invariant locking transaction boundary rollback strategy error reporting side effects ``` 一个通用 `write()` 不可能替所有业务正确猜出这些规则。 ### 示例 ```cpp auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>(); auto old_min = guard.get<&Device::min_speed>(); auto old_max = guard.get<&Device::max_speed>(); guard.set<&Device::min_speed>(candidate_min); guard.set<&Device::max_speed>(candidate_max); if (candidate_min > candidate_max) { guard.set<&Device::min_speed>(old_min); guard.set<&Device::max_speed>(old_max); } ``` 业务操作自己拥有 invariant 和 rollback 语义。 ### 原则 **不要把 `write()` 变成隐藏事务引擎。** ## 13. Synchronization 就只负责 Synchronization Synchronization plan 的本质是把 property 映射到 lock slot,支持 independent、shared、unsynchronized、grouped 等配置。 Group 表示一致性域:同一个 group 的 property 使用同一个 mutex slot。 多属性锁会对 slot 去重,并按稳定 slot 顺序获取锁。 ### Synchronization 不等于 - validation; - transaction; - rollback; - event emission; - dirty tracking; - persistence commit。 这些能力可以构建在 Structive 上层,但不能偷偷和锁耦合。 ## 14. Computed Property 必须明确一致性边界 `computed_property` 通过 synchronized view 读取依赖项。这个 view 只允许读取和 computed property 本身处于同一个 resolved lock slot 的属性。 所以依赖 `min_speed`、`max_speed` 的 `speed_span` 应该和它们进入同一个同步组: ```cpp synchronization( sync_all_independent, sync_group("speed", "min_speed", "max_speed", "speed_span") ) ``` 这样一致性关系由同步计划明确表达,而不是让 getter 随意读取任意字段。 ### Trusted Accessor `trusted_computed_property` 和 `trusted_accessor_property` 会直接调用对象上的受信任成员函数,设计上绕开 synchronized view 的依赖检查。 ### 原则 **默认优先 synchronized computed property。只有 accessor 自身明确拥有或保证同步语义时才使用 trusted accessor。** ## 15. Runtime Access 是边界能力 `Property_Object_Base` 提供 type-erased runtime interface,核心输入包括: - string key; - 不传 mode 时使用 intrinsic access; - 只有选择 `external` 或 `persistence` 投影时才使用 `Managed_Access_Mode`; - `std::type_info`; - 显式 result code。 它适用于编译期不知道具体类型的 adapter。 它不应该成为把所有正常 typed C++ 代码动态化的理由。 ### 原则 **能在编译期确定的代码就留在编译期;只有真正跨动态边界时才进入 runtime path。** ## 16. Compile-time Error 和 Runtime Error 分工明确 Structive 对静态可知问题尽量在编译期拒绝: - member 未注册; - key 重复; - 同一成员存储被重复注册; - single-valued category 重复; - 非 inheritable Attribute 被放入 `defaults(...)`; - accessor 实际能力与声明 capability 冲突。 Runtime failure 留给 runtime 输入和 runtime 配置: - 动态 key 不存在; - 动态属性对该 view 不可访问; - synchronization plan 引用了未知 key; - runtime 同步规则重复配置同一个 property; - runtime type mismatch。 ### 原则 **静态可知的 Schema 错误不要拖到运行时;真正动态的 adapter 输入也不要硬伪装成编译期问题。** ## 17. 同步策略必须保持可替换 `Property_Object` 支持同步策略,默认是 `Shared_Mutex_Policy`,同时提供 `No_Lock_Policy`。 这是重要架构缝隙。Core 语义不应该和某一种 mutex 或某一个全局 scheduler 焊死。 `No_Lock_Policy` 只是取消真实互斥,不代表并发访问自动安全。 ## 18. Extension 设计规则 一个好的 Structive Extension 应该: 1. 定义属于自己领域的 category。 2. 复用 Core Attribute 协议。 3. 只解释自己拥有或明确依赖的 category。 4. 依赖 Core,而不是要求 Core 反向依赖自己。 5. 把领域 fallback 行为留在扩展里。 6. 优先消费已有 Schema,不复制第二棵 descriptor tree。 7. 不因为 adapter 需要便利规则就修改 Core 基础语义。 ## 19. Core 的非目标 Structive Core 不应该同时变成: - ORM; - JSON 库; - GUI 框架; - RPC 框架; - signal/slot 系统; - 事务引擎; - 替代所有语言能力的“万能反射”。 Structive 应该提供足够强的结构契约,让这些系统来消费它。 ## 20. 演进原则 修改库时依次问: 1. 这是 structural model、instance management 还是 extension 的职责? 2. 这个新概念本质上是不是一个 Attribute category,而不是新的 metadata channel? 3. 这个错误能否在编译期发现? 4. 修改后是否仍然保留原生 C++ 成员语义? 5. Raw access 与 managed access 的边界是否仍然清晰? 6. 是否把 synchronization 和 validation、transaction、event 混在一起了? 7. Core 是否开始理解本应属于 Extension 的领域概念? 8. Member pointer 是否仍然是 typed API 最自然的身份? 9. 新的 convenience API 是否重复制造了另一条同语义路径? 10. 是否保持了已有函数签名和功能语义,而不是悄悄改义? 如果一个功能连续违反这些问题,应先重新考虑它属于哪一层,而不是立刻实现。 ## 21. 架构总结 Structive 最强的形态应该是少量强概念,而不是大量魔法便利接口: ```text 普通 C++ 类型 + 显式 Schema + 一套 Attribute 模型 + 显式 Capability + 显式 Validation + 显式 Synchronization + 可选 Managed Instance 行为 + Extension 自己解释自己的语义 ``` 当泛型系统能够理解业务类型、却不需要接管业务类型时,Structive 的价值最大。