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