添加文档

This commit is contained in:
2026-08-07 23:55:52 +08:00
parent a5041e10bc
commit 6ac872b9b7
2 changed files with 886 additions and 0 deletions
+859
View File
@@ -0,0 +1,859 @@
# Adminive 设计理念与架构原则
本文描述 Adminive 当前架构的设计目标、职责边界和长期演进原则。它不是 API 速查表;具体接口和完整示例仍以项目根目录 `README.md` 为准。
## 1. 项目定位
Adminive 的目标不是重新发明对象模型、反射系统或属性系统,而是把后端已经存在的 C++ 结构投影成后台管理能力。
可以把两层职责概括为:
```text
Structive
描述并管理“这个 C++ 对象本身是什么”
Adminive
描述“后台管理系统应该怎样展示、编辑、传输和提交这个对象”
```
因此 Adminive 更接近一个 backend-driven administration projection layer,而不是一个新的 Property Core。
它围绕同一份后端描述连接这些领域:
```text
C++ business type
Adminive descriptor
├── JSON encode / decode
├── frontend schema
├── AMIS CRUD / form / table
├── 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-onlyStructive 可以直接利用这个事实:
```text
read_only
managed write 不存在
不进入 writable synchronization topology
不贡献 lock slot
不创建对应 mutex
managed read 走零锁路径
```
这类优化属于结构本身,因此应由 Structive 完成。
### 3.2 Adminive 负责什么
Adminive 负责后台管理领域信息和适配:
```text
字段名称与显示名称
editable / creatable
sensitive
required
widget
visible / visible_on
list column metadata
排序信息
JSON 描述协议
AMIS schema
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
是否允许前端编辑
是否允许创建时输入
是否在列表中展示
是否敏感
使用什么 widget
显示什么 label
```
例如:
```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. 默认只读,显式声明可写
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。
同步、事务和适配器可以据此建立正确行为。
## 6. 四种字段声明语义
### 6.1 普通字段
```cpp
ADMINIVE_FIELD(Config, id)
```
语义:
```text
intrinsic read-only
不产生 managed write
不为自身贡献 Structive mutex
```
它仍然是普通 C++ 成员。直接修改 raw member 属于 raw C++ path,不属于 Adminive/Structive managed contract。
### 6.2 `.editable()`
```cpp
ADMINIVE_FIELD(Config, worker_count).editable()
```
语义:
```text
intrinsic read-write
frontend update editable
默认参加 Structive synchronization
```
这是后台表单最常见的可编辑字段。
### 6.3 `.creatable()`
```cpp
ADMINIVE_FIELD(Device, address).creatable()
```
语义:
```text
intrinsic read-write
允许 create input
默认参加 Structive synchronization
```
它表达创建流程的领域能力,不等同于普通 update editable。
### 6.4 `.read_write()`
```cpp
ADMINIVE_FIELD(Config, backend_state).read_write()
```
语义:
```text
intrinsic read-write
不因为这一声明自动开放 frontend edit
默认参加 Structive synchronization
```
适合配置加载、后台代码、服务内部更新等受控路径。
### 6.5 `.unsynchronized()`
```cpp
ADMINIVE_FIELD(Config, atomic_counter).read_write().unsynchronized()
```
语义:
```text
intrinsic writable
不为该路径创建 Structive mutex
```
只有以下情况适合使用:
```text
字段自身已经是原子类型
更高层已经提供同步
对象生命周期保证单线程
或业务明确接受无同步访问
```
`.unsynchronized()` 不是性能开关,更不是“我觉得这里应该没事”。它是对同步责任的显式转移。
## 7. 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++ 内存级权限机制。
## 8. 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++ 结构,而不是建立一堵无法绕过的对象墙。
## 9. 为什么 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 限制,而是锁生命周期本身决定的安全边界。
## 10. 同步模型:只保护 mutable consistency
Structive synchronization 只解决一个问题:
> 当前进程内,对可变对象状态进行 managed access 时如何保持一致性。
它不负责:
```text
HTTP 权限
数据库事务
配置文件原子替换
业务 validator
外部设备写入
前端确认流程
```
默认情况下,可写属性可以拥有独立同步域,以保留并发性。
当一个具体对象实例确实要求跨多个属性的一致快照时,可以使用 shared topology,例如 `Config_Store` 对完整根配置的持久化场景。
这种选择应该由一致性需求驱动,而不是为了“简单”把所有对象默认放到一把全局锁下。
## 11. 嵌套结构的同步原则
嵌套结构不能简单采用“父对象只要有一个 writable 子字段,所有后代访问都锁父对象”的粗粒度规则。
Adminive/Structive 应尽量利用真实 intrinsic information
```text
只读叶子
→ 不应因为 writable sibling 而产生无意义锁
unsynchronized writable leaf
→ 不应因为同级字段可写而自动获取无关父锁
可整体替换的 writable ancestor
→ ancestor 的一致性域覆盖其子树
```
最后一条很重要:如果祖先字段本身允许整体赋值,那么对子树任意成员的访问都可能与祖先整体替换发生竞争,此时祖先同步域自然必须参与。
同步边界来自真实 mutation possibility,而不是单纯来自对象树层级。
## 12. 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 系统。
## 13. 三种“事务”不能混为一谈
Adminive 中至少存在三类不同一致性问题:
### 13.1 内存对象同步
由 Structive synchronization 负责。
```text
shared/unique lock
lock slot
synchronization topology
```
### 13.2 Adminive 外部副作用事务
`Resource_Transaction<Model>` 负责。
```text
prepare
commit
rollback
```
它可以代表配置文件、设备、远端服务等外部动作。
### 13.3 数据库事务
由数据库自身负责。
数据库 transaction 不能替代进程内对象锁;对象 mutex 也不能替代数据库 ACID。
三者可以在一次业务流程中协作,但职责必须保持独立。
## 14. Adapter First
Adminive Core 不应直接绑定具体第三方库。
当前设计通过 Adapter 连接外部生态:
```text
Json_Adapter<Json>
Value_Adapter<T, Json>
Enum_Adapter<Enum>
Reflection_Adapter<T>
Object_Adapter<T>
Control_Adapter<T, Json>
Polymorphic_Adapter<T, Json>
HTTP adapter
```
这条原则有几个直接收益:
1. 核心协议不因为换 JSON 库而变化。
2. 枚举反射和结构体反射可以独立替换。
3. Runtime object 不必为了框架继承某个基类。
4. 第三方依赖停留在桥接层,不污染业务类型。
5. 可以针对单个类型提供精确适配,而不是扩大 Core 特例。
如果未来接入新的 JSON/HTTP/Reflection 实现,应优先增加 Adapter,而不是修改 Adminive 的中心算法。
## 15. Object_Adapter:运行时对象与配置模型分离
不是所有运行时对象都适合作为可复制配置对象。
例如对象可能包含:
```text
atomic
mutex
socket
thread
runtime cache
file handle
```
`Object_Adapter<T>` 的意义是建立:
```text
Runtime Object
Pure Model
```
Adminive 通过 model 完成:
```text
JSON
validation
patch
transaction candidate
frontend schema
```
然后由 `commit()` 将结果应用回真正的 runtime object。
这比要求所有业务类型变成“为了后台框架而设计的 DTO”更符合低侵入原则。
## 16. Frontend 应保持被动
Adminive 是 backend-driven 系统,因此前端不应该重新维护:
```text
字段名称
业务枚举
显示条件
校验规则
CRUD schema
配置树结构
```
前端的职责是:
```text
获取后端描述
渲染页面
提交用户输入
展示后端错误
```
如果一个业务字段修改后必须同时修改 C++ descriptor 和 TypeScript hard-coded schema,说明 backend-driven 边界已经被破坏。
前端可以拥有纯 UI 行为,但不应该成为第二份业务 Schema。
## 17. 编译期与运行期的分工
Adminive 建议遵守以下规则:
```text
编译期已知
→ template / concept / requires / descriptor type
运行期才知道
→ JSON key / HTTP input / runtime resource / dynamic value
```
例如 managed writability 是 descriptor type 的静态事实,不应该等一次 HTTP 请求进入以后才通过字符串判断它到底是不是 writable。
而 JSON 字段是否存在、用户输入是否合法,则天然只能在运行时处理。
这可以同时获得:
```text
更早的错误
更清晰的 API
更少的运行时分支
更好的优化机会
```
## 18. Metadata 必须具有真实语义
对 Adminive 来说,metadata 不应该只是“最终转成 JSON 的标签集合”。
高质量 metadata 应至少满足一个条件:
> 它能被某个明确的系统消费,并改变真实行为。
例如:
```text
read_only
→ 改变 Structive capability 和同步成本
editable
→ 改变 update schema 和 managed mutability
sensitive
→ 改变序列化/默认值暴露行为
required
→ 改变输入验证
widget
→ 改变前端控件
```
如果新增 metadata 没有明确消费者,应谨慎加入 Core descriptor,避免逐渐变成无边界的“万能标签袋”。
## 19. Core 的非目标
Adminive Core 不应该演化成:
```text
通用 ORM
通用 RPC framework
权限系统
数据库 abstraction
GUI framework
全功能 serialization library
Structive 的替代品
全局 transaction manager
```
这些领域可以通过 Adapter 或上层模块与 Adminive 协作,但不应因为某个项目需要就全部进入核心协议。
保持非目标清晰,是长期保持 API 简洁的重要手段。
## 20. 新功能应该放在哪里
新增能力时,可以按下面的判断顺序决定归属。
### 20.1 它描述 Property 本身吗?
例如:
```text
intrinsic readable/writable
synchronization topology
generic constraint
```
优先考虑 Structive。
### 20.2 它描述后台管理领域吗?
例如:
```text
editable
widget
list column
AMIS schema
frontend visible condition
```
属于 Adminive。
### 20.3 它绑定具体第三方库吗?
例如:
```text
nlohmann::json
Drogon
cpp-httplib
magic_enum
Boost.PFR
```
放 Adapter/bridge 层。
### 20.4 它属于具体业务项目吗?
例如:
```text
某设备必须先断电才能修改配置
某用户角色才能执行操作
某数据库表的迁移规则
```
留在业务层,不进入 Adminive Core。
## 21. 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. 对行为语义的修改必须同时补契约测试和文档。
## 22. 测试原则
Adminive 的测试不应只验证“JSON 长得对”。
至少应该覆盖四类契约:
### 22.1 Descriptor contract
```text
字段 metadata
managed writable
synchronized
nested descriptor
schema validation
```
### 22.2 Managed synchronization contract
```text
read-only zero-lock
writable locking
unsynchronized path
nested lock propagation
callback lifetime
multi-thread blocking relationship
```
### 22.3 Adapter contract
```text
JSON encode/decode
Enum Adapter
Reflection Adapter
Object Adapter
HTTP Adapter
```
### 22.4 Transaction contract
```text
prepare failure
runtime commit failure
external commit failure
rollback
snapshot consistency
Config Store persistence
```
这些测试保护的是设计边界,而不仅是当前实现细节。
## 23. 设计判断清单
在提交一个新设计前,可以快速检查:
```text
[ ] 这是 Structive 已经解决的问题吗?
[ ] 这是 Adminive 后台领域真正需要的概念吗?
[ ] 它是否把第三方库耦合进 Core?
[ ] 它是否让 metadata 真正改变行为?
[ ] 它是否把访问控制错误塞进 Property Core
[ ] 它是否创建了第二套同步或事务机制?
[ ] 静态事实是否能在编译期表达?
[ ] 普通用户是否必须理解这个高级机制?
[ ] 是否存在更合适的 Adapter 层?
[ ] 是否补充了对应契约测试和文档?
```
如果多数问题无法明确回答,通常意味着抽象边界还没有收敛。
## 24. 最终架构图
```text
C++ Business Types
Adminive Descriptor
┌─────────────────┴─────────────────┐
│ │
▼ ▼
Adminive domain metadata Structive schema
editable/widget/sensitive/... capability/synchronization
│ │
└─────────────────┬─────────────────┘
Managed object view
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
JSON Adapter Resource Service AMIS Schema
│ │ │
▼ ▼ ▼
serialization HTTP transaction Frontend
Resource_Transaction
prepare/commit/rollback
```
最重要的依赖方向始终是:
```text
Structive
Adminive Core
Adapters / Service
Application
```
底层不知道上层领域,上层消费底层能力。这是 Adminive 后续演进时最需要守住的架构边界。