# Adminive 设计理念与架构原则 本文描述 Adminive 当前架构的设计目标、职责边界和长期演进原则。它不是 API 速查表;具体接口和完整示例仍以项目根目录 `README.md` 为准。 ## 1. 项目定位 Adminive 的目标不是重新发明对象模型、反射系统或属性系统,而是把后端已经存在的 C++ 结构投影成后台管理能力。 可以把职责分成结构层、字段呈现层、视图组合层和最终渲染层: ```text Structive / Object Schema 描述并管理“这个 C++ 对象本身是什么” │ ▼ Field Presentation 描述“单个字段应该以什么语义控件呈现” │ ▼ View Schema / Composition 描述“字段和业务组件如何组成表单、表格和页面” │ ▼ AMIS Adapter / Frontend 把语义结构翻译成具体 UI,并负责纯视觉与响应式细节 ``` 因此 Adminive 更接近一个 backend-driven administration projection layer,而不是一个新的 Property Core。 它围绕同一份后端描述连接这些领域: ```text C++ business type │ ▼ Adminive descriptor │ ├── JSON encode / decode ├── field presentation ├── form / table view schema ├── page composition ├── AMIS adapter ├── HTTP resource service ├── validation / error path ├── managed update └── external transaction ``` 前端消费后端生成的描述结果,而不是重新维护一份业务字段定义。 ## 2. 最核心的设计原则 Adminive 的核心原则可以浓缩为以下几条: 1. **Structive 管结构事实,Adminive 管后台管理语义。** 2. **属性信息必须能够影响真实行为,而不只是生成 UI 文本。** 3. **默认只读,显式声明可写。** 4. **同步只保护 mutable consistency,不承担权限、事务或持久化职责。** 5. **JSON、HTTP、枚举反射、结构体反射都是 Adapter,不进入核心协议。** 6. **外部系统是否暴露字段由外部系统自己决定,Property Core 不做访问控制。** 7. **静态可知的事实尽量在编译期表达,动态输入才留到运行期处理。** 8. **前端是后端描述的消费者,不成为第二个业务规则中心。** 9. **外部副作用事务与内存对象同步保持正交。** 10. **不为了统一形式制造重复抽象;已经由 Structive 解决的问题不在 Adminive 再实现一次。** 后续新增功能如果违反其中任意一条,都应该先重新确认它是否真的属于 Adminive Core。 ## 3. Structive 与 Adminive 的边界 ### 3.1 Structive 负责什么 `third_party/Structive` 负责对象自身的结构能力: ```text Property intrinsic capability Schema read / write read-only optimization synchronization topology lock slot mutex storage guard runtime property access constraint / generic attribute infrastructure ``` 这些能力与 HTTP、JSON、AMIS、数据库、GUI 没有关系。 例如一个 Property 被声明为 intrinsic read-only,Structive 可以直接利用这个事实: ```text read_only ↓ managed write 不存在 ↓ 不进入 writable synchronization topology ↓ 不贡献 lock slot ↓ 不创建对应 mutex ↓ managed read 走零锁路径 ``` 这类优化属于结构本身,因此应由 Structive 完成。 ### 3.2 Adminive 负责什么 Adminive 负责后台管理领域信息和适配: ```text 字段名称与 Field Presentation editable / creatable sensitive required control / visible_on Form View / Table View vertical / horizontal / flow / grid / group / card / tabs 表格列、排序、搜索、筛选、固定列与重排语义 Composition View / Slot JSON 描述协议 AMIS adapter HTTP resource semantics frontend patch Object_Adapter Resource_Transaction ``` 这些信息表达的是“后台系统怎样消费一个结构”,而不是改变 C++ Property 的基本定义。 ### 3.3 不允许再次出现第二套 Property Core Adminive 不应该重新拥有: ```text 另一套 Property Descriptor 另一套 per-field mutex tree 另一套 lock slot 另一套 managed access mode 另一套 runtime type erasure core ``` 如果某个需求本质上属于 Property/Schema/synchronization,应优先落到 Structive;如果它属于后台管理领域,则留在 Adminive。 这种分层避免两个库同时维护相似概念并逐渐产生不同语义。 ## 4. “结构事实”与“后台元数据”必须分开 Adminive 字段同时包含两类信息,但这两类信息不能混为一谈。 第一类是结构事实: ```text 这个字段在 managed model 中是否 writable 这个字段是否参与 synchronization 字段值的真实 C++ 类型 字段 accessor ``` 第二类是后台领域信息,但它本身还必须继续分层: ```text Field Presentation label / description / control / options / visible_on View Schema 字段选择 / 顺序 / group / card / grid / flow / tabs table columns / sort / search / filter / fixed / reorder Composition View Form / Table / Status 等完整组件之间的页面组合 ``` 字段自身的呈现信息不能携带“它在某个表格里排第几列”这种视图语义;同一个 Property 可以同时出现在 Edit View、Detail View 和多个 Table View 中,而不需要重新定义字段本身。 例如: ```cpp ADMINIVE_FIELD(Config, backend_value).read_write() ``` 表示 `backend_value` 在 Adminive managed model 中可以被修改,但并没有因此自动成为前端可编辑字段。 而: ```cpp ADMINIVE_FIELD(Config, frontend_value).editable() ``` 既使它成为 managed writable,也附加“前端 update 可以编辑”的领域语义。 因此: ```text managed writable ≠ frontend editable ``` 这条边界必须长期保持。 ## 5. 后端 UI 的分层模型 Adminive 的目标是让后端修改 C++ 结构和后台描述后,前端无需同步维护业务字段清单即可得到新的界面。因此后端必须拥有页面的**语义结构**,但不应该把所有语义都塞进 Field Descriptor,也不应该直接把 AMIS JSON 当成业务描述语言。 ### 5.1 Object Schema:字段是什么 Object Schema 由 Structive 与 Adminive Descriptor 共同建立,负责字段名、真实 C++ 类型、intrinsic writable、constraint、managed synchronization 等结构事实。这一层不知道表格、Card、Tabs 或 AMIS。 ### 5.2 Field Presentation:一个字段怎么呈现 `Field_Presentation` 只描述单字段呈现: ```text label description control visible_on enum labels/options ``` `control` 使用 `text / multiline_text / number / boolean / select / date / color` 等语义类型,而不是把 `input-text`、`input-date` 之类 AMIS 组件名写进结构描述。AMIS Adapter 再负责语义控件到 AMIS 组件的映射。 ### 5.3 View Schema:这些字段怎么组成一个业务视图 Form View 可以组合: ```text all_fields field vertical horizontal flow grid group card tabs / tab ``` 后端因此仍然可以完整决定“这十几个字段如何拼接”,但组合关系不再污染字段定义。一个类型可以分别拥有 Edit/Create/Detail View。 Table View 是一等视图,而不是普通 Field Presentation 的附属标记。它负责: ```text 哪些字段成为列 列顺序与标题 fixed left/right sortable searchable filterable default sort row reorder create/edit form layout ``` 这里的查询能力同时约束后端 Collection Service。前端不能因为自己画了一个搜索框,就让一个未声明为 searchable/filterable 的字段获得后端查询能力。 ### 5.4 Composition View:完整业务组件怎么组成页面 当页面需要组合已经成型的 Form、Table、Status 或其他业务组件时,使用独立 `Composition_View`。它提供: ```text slot heading vertical horizontal flow grid group card tabs / tab ``` `slot` 只是引用一个已经生成的业务组件。例如“上方配置表单 + 下方设备表格”可以由后端描述为一个 vertical composition,而不需要业务代码直接拼 AMIS `grid/card/tpl` JSON。 ### 5.5 后端与前端的最终边界 后端负责: ```text 字段存在与否 字段 intrinsic capability 单字段 presentation 字段属于哪个视图 字段顺序与逻辑分组 form/table/page 的语义组合 表格排序、搜索、筛选等后端能力 ``` 前端负责: ```text 具体 renderer CSS / theme 响应式断点 最终列宽和像素级间距 设备尺寸下的视觉降级 ``` 因此核心原则是: > **后端拥有页面的语义结构,前端拥有页面的视觉实现。** 这样既满足“删字段、加字段、改配置只改后端”,又避免后端被某个具体前端框架的 CSS/Grid 实现锁死。 ### 5.6 AMIS 只是 Adapter 依赖方向必须保持: ```text Object Schema ↓ Field Presentation ↓ View Schema / Composition ↓ AMIS Adapter ↓ AMIS JSON ``` 不能反过来让 `Field_Descriptor` 直接保存任意 AMIS JSON;否则 Adminive 会从 backend-driven schema 退化成“在 C++ 里写前端 JSON”。 ## 6. 默认只读,显式声明可写 Adminive 字段默认是 intrinsic read-only。 这是刻意选择,而不是缺省值碰巧如此。 理由有三点: ### 5.1 安全的默认语义 后台描述中出现一个字段,并不意味着 HTTP/GUI 更新应该天然拥有修改它的能力。 新增字段时默认只读,可以避免因为“忘记配置”而无意进入 managed mutation path。 ### 5.2 让 metadata 真正参与性能优化 只读信息会一路传递到 Structive,同步系统因此可以跳过无意义的锁资源。 这比“所有字段都建 mutex,只是在 write 时检查权限”更符合结构描述系统的价值。 ### 5.3 writable 是结构承诺 一旦调用: ```cpp .editable() .creatable() .read_write() ``` 就意味着 Adminive 正式承认这个字段存在 managed mutation path。 同步、事务和适配器可以据此建立正确行为。 ## 7. 四种字段声明语义 ### 7.1 普通字段 ```cpp ADMINIVE_FIELD(Config, id) ``` 语义: ```text intrinsic read-only 不产生 managed write 不为自身贡献 Structive mutex ``` 它仍然是普通 C++ 成员。直接修改 raw member 属于 raw C++ path,不属于 Adminive/Structive managed contract。 ### 7.2 `.editable()` ```cpp ADMINIVE_FIELD(Config, worker_count).editable() ``` 语义: ```text intrinsic read-write frontend update editable 默认参加 Structive synchronization ``` 这是后台表单最常见的可编辑字段。 ### 7.3 `.creatable()` ```cpp ADMINIVE_FIELD(Device, address).creatable() ``` 语义: ```text intrinsic read-write 允许 create input 默认参加 Structive synchronization ``` 它表达创建流程的领域能力,不等同于普通 update editable。 ### 7.4 `.read_write()` ```cpp ADMINIVE_FIELD(Config, backend_state).read_write() ``` 语义: ```text intrinsic read-write 不因为这一声明自动开放 frontend edit 默认参加 Structive synchronization ``` 适合配置加载、后台代码、服务内部更新等受控路径。 ### 7.5 `.unsynchronized()` ```cpp ADMINIVE_FIELD(Config, atomic_counter).read_write().unsynchronized() ``` 语义: ```text intrinsic writable 不为该路径创建 Structive mutex ``` 只有以下情况适合使用: ```text 字段自身已经是原子类型 更高层已经提供同步 对象生命周期保证单线程 或业务明确接受无同步访问 ``` `.unsynchronized()` 不是性能开关,更不是“我觉得这里应该没事”。它是对同步责任的显式转移。 ## 8. Adminive 不做访问控制系统 Adminive 中的 `editable`、`readable`、`sensitive` 等信息服务于具体后台适配语义,但它们不构成一个通用安全边界。 Structive 更不会定义: ```text internal external persistence RPC GUI admin user ``` 这类访问角色。 原因是不同消费者拥有不同边界: ```text GUI HTTP API RPC plugin script debug tooling persistence ``` 是否向某个消费者暴露字段,应由那个消费者或更高层业务策略决定。 因此 Adminive 的设计原则是: > Property 描述自身能做什么;外部系统决定自己允许做什么。 `sensitive()` 可以指导 Adminive JSON/HTTP 等适配器避免泄漏敏感内容,但不应被误解为 C++ 内存级权限机制。 ## 9. Managed path 与 raw C++ path Adminive 不试图取代普通 C++ 对象。 例如: ```cpp config.worker_count = 8; ``` 如果成员本身允许直接访问,这仍然是合法 C++。 而: ```cpp managed.member<&Config::worker_count>().write([](auto& value) { value = 8; }); ``` 属于 managed path。 managed path 才承诺: ```text 遵守 intrinsic writable 遵守 Structive synchronization 维持回调生命周期边界 与 Resource_Service 的事务流程协作 ``` 直接绕过 managed API 修改 raw member,也同时绕过这些保证。 这符合 Adminive/Structive 的共同原则: > 增强原生 C++ 结构,而不是建立一堵无法绕过的对象墙。 ## 10. 为什么 Managed 回调不能泄漏引用 Managed read/write 的锁只在回调作用域中有效。 如果允许: ```cpp const auto& value = field.read([](const auto& current) -> const auto& { return current; }); ``` 那么引用在返回后仍存在,但锁已经释放,同步契约就失效了。 因此 Adminive 在编译期拒绝这些回调结果: ```text reference pointer reference_wrapper ranges view ``` 需要把结果带出 managed scope 时,应: ```text 返回值副本 或使用 snapshot() ``` 这不是 API 限制,而是锁生命周期本身决定的安全边界。 ## 11. 同步模型:只保护 mutable consistency Structive synchronization 只解决一个问题: > 当前进程内,对可变对象状态进行 managed access 时如何保持一致性。 它不负责: ```text HTTP 权限 数据库事务 配置文件原子替换 业务 validator 外部设备写入 前端确认流程 ``` 默认情况下,可写属性可以拥有独立同步域,以保留并发性。 当一个具体对象实例确实要求跨多个属性的一致快照时,可以使用 shared topology,例如 `Config_Store` 对完整根配置的持久化场景。 这种选择应该由一致性需求驱动,而不是为了“简单”把所有对象默认放到一把全局锁下。 ## 12. 嵌套结构的同步原则 嵌套结构不能简单采用“父对象只要有一个 writable 子字段,所有后代访问都锁父对象”的粗粒度规则。 Adminive/Structive 应尽量利用真实 intrinsic information: ```text 只读叶子 → 不应因为 writable sibling 而产生无意义锁 unsynchronized writable leaf → 不应因为同级字段可写而自动获取无关父锁 可整体替换的 writable ancestor → ancestor 的一致性域覆盖其子树 ``` 最后一条很重要:如果祖先字段本身允许整体赋值,那么对子树任意成员的访问都可能与祖先整体替换发生竞争,此时祖先同步域自然必须参与。 同步边界来自真实 mutation possibility,而不是单纯来自对象树层级。 ## 13. Resource_Service 的职责 `Resource_Service` 是 Adminive 的应用事务编排层,不是新的 Property Core。 一次 managed update 可以包含: ```text 取得一致性快照 应用 frontend patch 校验候选对象 prepare external state 提交 runtime model commit external side effect 失败时恢复 生成最终 response snapshot ``` 当绑定 `Managed_Value` / `Managed_Field` 时,对象一致性由 Structive synchronization 保证。 `Resource_Service` 不应该再创建一棵重复的 per-field mutex 系统。 ## 14. 三种“事务”不能混为一谈 Adminive 中至少存在三类不同一致性问题: ### 14.1 内存对象同步 由 Structive synchronization 负责。 ```text shared/unique lock lock slot synchronization topology ``` ### 14.2 Adminive 外部副作用事务 由 `Resource_Transaction` 负责。 ```text prepare commit rollback ``` 它可以代表配置文件、设备、远端服务等外部动作。 ### 14.3 数据库事务 由数据库自身负责。 数据库 transaction 不能替代进程内对象锁;对象 mutex 也不能替代数据库 ACID。 三者可以在一次业务流程中协作,但职责必须保持独立。 ## 15. Adapter First Adminive Core 不应直接绑定具体第三方库。 当前设计通过 Adapter 连接外部生态: ```text Json_Adapter Value_Adapter Enum_Adapter Reflection_Adapter Object_Adapter Control_Adapter Polymorphic_Adapter HTTP adapter ``` 这条原则有几个直接收益: 1. 核心协议不因为换 JSON 库而变化。 2. 枚举反射和结构体反射可以独立替换。 3. Runtime object 不必为了框架继承某个基类。 4. 第三方依赖停留在桥接层,不污染业务类型。 5. 可以针对单个类型提供精确适配,而不是扩大 Core 特例。 如果未来接入新的 JSON/HTTP/Reflection 实现,应优先增加 Adapter,而不是修改 Adminive 的中心算法。 ## 16. Object_Adapter:运行时对象与配置模型分离 不是所有运行时对象都适合作为可复制配置对象。 例如对象可能包含: ```text atomic mutex socket thread runtime cache file handle ``` `Object_Adapter` 的意义是建立: ```text Runtime Object ⇅ Pure Model ``` Adminive 通过 model 完成: ```text JSON validation patch transaction candidate frontend schema ``` 然后由 `commit()` 将结果应用回真正的 runtime object。 这比要求所有业务类型变成“为了后台框架而设计的 DTO”更符合低侵入原则。 ## 17. Frontend 应保持被动 Adminive 是 backend-driven 系统,因此前端不应该重新维护: ```text 字段名称 业务枚举 显示条件 校验规则 CRUD schema 配置树结构 ``` 前端的职责是: ```text 获取后端描述 渲染页面 提交用户输入 展示后端错误 ``` 如果一个业务字段修改后必须同时修改 C++ descriptor 和 TypeScript hard-coded schema,说明 backend-driven 边界已经被破坏。 前端可以拥有纯 UI 行为,但不应该成为第二份业务 Schema。 ## 18. 编译期与运行期的分工 Adminive 建议遵守以下规则: ```text 编译期已知 → template / concept / requires / descriptor type 运行期才知道 → JSON key / HTTP input / runtime resource / dynamic value ``` 例如 managed writability 是 descriptor type 的静态事实,不应该等一次 HTTP 请求进入以后才通过字符串判断它到底是不是 writable。 而 JSON 字段是否存在、用户输入是否合法,则天然只能在运行时处理。 这可以同时获得: ```text 更早的错误 更清晰的 API 更少的运行时分支 更好的优化机会 ``` ## 19. Metadata 必须具有真实语义 对 Adminive 来说,metadata 不应该只是“最终转成 JSON 的标签集合”。 高质量 metadata 应至少满足一个条件: > 它能被某个明确的系统消费,并改变真实行为。 例如: ```text read_only → 改变 Structive capability 和同步成本 editable → 改变 update schema 和 managed mutability sensitive → 改变序列化/默认值暴露行为 required → 改变输入验证 Field Presentation control → 改变单字段呈现控件 View Schema → 改变字段组合、表格查询能力和页面语义结构 ``` 如果新增 metadata 没有明确消费者,应谨慎加入 Core descriptor,避免逐渐变成无边界的“万能标签袋”。 ## 20. Core 的非目标 Adminive Core 不应该演化成: ```text 通用 ORM 通用 RPC framework 权限系统 数据库 abstraction GUI framework 全功能 serialization library Structive 的替代品 全局 transaction manager ``` 这些领域可以通过 Adapter 或上层模块与 Adminive 协作,但不应因为某个项目需要就全部进入核心协议。 保持非目标清晰,是长期保持 API 简洁的重要手段。 ## 21. 新功能应该放在哪里 新增能力时,可以按下面的判断顺序决定归属。 ### 21.1 它描述 Property 本身吗? 例如: ```text intrinsic readable/writable synchronization topology generic constraint ``` 优先考虑 Structive。 ### 21.2 它描述后台管理领域吗? 例如: ```text editable Field Presentation Form/Table View Composition View AMIS adapter frontend visible condition ``` 属于 Adminive。 ### 21.3 它绑定具体第三方库吗? 例如: ```text nlohmann::json Drogon cpp-httplib magic_enum Boost.PFR ``` 放 Adapter/bridge 层。 ### 21.4 它属于具体业务项目吗? 例如: ```text 某设备必须先断电才能修改配置 某用户角色才能执行操作 某数据库表的迁移规则 ``` 留在业务层,不进入 Adminive Core。 ## 22. API 演进原则 后续修改 Adminive API 时建议遵守: 1. 不破坏“Structive 管结构,Adminive 管后台领域”的依赖方向。 2. 不重新引入重复同步系统。 3. 不把 frontend permission 当成 Property intrinsic capability。 4. 静态事实使用类型系统表达,不退化成运行时 bool 判断。 5. Runtime adapter 保持动态,但不要把动态复杂度传播到普通 typed API。 6. 一个概念只保留一套主要表达方式,避免 `_key`、mode、view 等重复表面接口无边界增长。 7. 不为了少几个函数制造难以理解的模板 DSL。 8. metadata 必须有清晰消费者。 9. 高级能力允许高级 API,但应与普通使用路径分层。 10. 对行为语义的修改必须同时补契约测试和文档。 ## 23. 测试原则 Adminive 的测试不应只验证“JSON 长得对”。 至少应该覆盖五类契约: ### 23.1 Descriptor contract ```text 字段 metadata managed writable synchronized nested descriptor schema validation ``` ### 23.2 View contract ```text Field Presentation 与结构事实分离 Form View 的 group/card/grid/flow/tabs 组合 Table View 的列选择与顺序 sortable/searchable/filterable/fixed 的后端能力约束 Composition View 的 slot 与跨组件组合 /view JSON 与 AMIS Adapter 的语义一致性 ``` ### 23.3 Managed synchronization contract ```text read-only zero-lock writable locking unsynchronized path nested lock propagation callback lifetime multi-thread blocking relationship ``` ### 23.4 Adapter contract ```text JSON encode/decode Enum Adapter Reflection Adapter Object Adapter HTTP Adapter AMIS Adapter ``` ### 23.5 Transaction contract ```text prepare failure runtime commit failure external commit failure rollback snapshot consistency Config Store persistence ``` 这些测试保护的是设计边界,而不仅是当前实现细节。 ## 24. 设计判断清单 在提交一个新设计前,可以快速检查: ```text [ ] 这是 Structive 已经解决的问题吗? [ ] 这是 Adminive 后台领域真正需要的概念吗? [ ] 它是否把第三方库耦合进 Core? [ ] 它是否把 Field Presentation、View Schema 和 AMIS Adapter 混成一层? [ ] 它是否让 metadata 真正改变行为? [ ] 它是否把访问控制错误塞进 Property Core? [ ] 它是否创建了第二套同步或事务机制? [ ] 静态事实是否能在编译期表达? [ ] 普通用户是否必须理解这个高级机制? [ ] 是否存在更合适的 Adapter 层? [ ] 是否补充了对应契约测试和文档? ``` 如果多数问题无法明确回答,通常意味着抽象边界还没有收敛。 ## 25. 最终架构图 ```text C++ Business Types │ ▼ Structive / Object Schema type / capability / synchronization │ ▼ Adminive Field Descriptor editable / required / sensitive │ ▼ Field Presentation Layer label / control / options / visible │ ┌──────────────┴──────────────┐ ▼ ▼ Form / Detail View Table View group/card/grid/flow/tabs columns/sort/search/filter │ │ └──────────────┬──────────────┘ ▼ Composition View Form/Table/Status slots + page layout │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ View JSON Resource Service AMIS Adapter │ ▼ Frontend │ renderer/theme/responsive layout JSON / HTTP / Enum / Reflection / AMIS 都位于消费或 Adapter 层; Structive 和字段结构层不知道具体前端框架。 ``` 最重要的依赖方向始终是: ```text Structive ↑ Adminive Core ↑ Adapters / Service ↑ Application ``` 底层不知道上层领域,上层消费底层能力。这是 Adminive 后续演进时最需要守住的架构边界。