1098 lines
32 KiB
Markdown
1098 lines
32 KiB
Markdown
# 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
|
||
frontend
|
||
vertical
|
||
horizontal
|
||
flow
|
||
grid
|
||
list
|
||
group
|
||
card
|
||
tabs / tab
|
||
```
|
||
|
||
后端因此仍然可以完整决定“这十几个字段如何拼接”,但组合关系不再污染字段定义。一个类型可以分别拥有 Edit/Create/Detail View。
|
||
|
||
Collection View 是集合展示的一等视图。`table/list/cards` 是同一个集合契约的三个 mode,共享字段、查询、CRUD、排序与 create/edit layout;list/cards 的 title/subtitle/body/accent 也属于 `adminive.view` 语义,不允许先生成 table AMIS 再回头修改 renderer JSON。`Table_View` 继续作为 Collection View 内部的表格/查询基础契约,并保留原有 API 语义。
|
||
|
||
Table View 负责:
|
||
|
||
```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
|
||
frontend
|
||
vertical
|
||
horizontal
|
||
flow
|
||
grid
|
||
list
|
||
group
|
||
card
|
||
tabs / tab
|
||
```
|
||
|
||
`slot` 只是引用一个已经生成的业务组件。例如“上方配置表单 + 下方设备表格”可以由后端描述为一个 vertical composition,而不需要业务代码直接拼 AMIS `grid/card/tpl` JSON。`list` 表示显式的纵向条目集合;`frontend` 则只声明子节点及其顺序,不声明最终布局,让前端根据设备、容器或产品形态决定横排、竖排、列表、卡片或表格等视觉组织。
|
||
|
||
当 `frontend` 交给独立前端消费时,只给 Composition View 仍不够,因为 slot 名本身不说明组件从哪里取得。因此正式外部协议使用 `adminive.composition-manifest`:Manifest 同时包含 Composition View 和 Slot Contract registry。每个 contract 可以声明 `component_kind`、`descriptor_api`、`view_api`、`data_api`、`amis_api` 以及当前 renderer schema。后端在 Manifest 边界验证 Composition 中使用的 slot 都有唯一 contract,前端只按 View 顺序和 Contract 渲染,不维护第二份 slot/业务字段表。
|
||
|
||
### 5.5 后端与前端的最终边界
|
||
|
||
后端负责:
|
||
|
||
```text
|
||
字段存在与否
|
||
字段 intrinsic capability
|
||
单字段 presentation
|
||
字段属于哪个视图
|
||
字段顺序与逻辑分组
|
||
form/collection/page 的语义组合
|
||
collection 排序、搜索、筛选等后端能力
|
||
slot contract 与组件来源
|
||
```
|
||
|
||
前端负责:
|
||
|
||
```text
|
||
具体 renderer
|
||
CSS / theme
|
||
响应式断点
|
||
frontend 节点的最终组合方式
|
||
最终列宽和像素级间距
|
||
设备尺寸下的视觉降级
|
||
```
|
||
|
||
因此核心原则是:
|
||
|
||
> **后端默认拥有页面的语义结构;当后端显式使用 frontend 节点时,只交付内容与顺序,由前端拥有该节点的最终定位;所有节点的视觉实现始终属于前端。**
|
||
|
||
这两个模式不能混淆:`vertical/horizontal/flow/grid/list/group/card/tabs` 表示后端决定组合语义,`frontend` 表示后端明确放弃该层的布局决定权。前端不能擅自把普通后端组合节点解释成另一种业务结构。
|
||
|
||
这样既满足“删字段、加字段、改配置只改后端”,又避免后端被某个具体前端框架的 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”。AMIS 表单提交同样只能由 View 与字段权限推导 payload:readonly 字段可以出现在初始展示数据里,但 submit `api.data` 只包含当前 View 选中的 `editable`/`creatable` 字段。这里不能通过放宽 Resource Service 去兼容前端整对象回传,否则会破坏默认只读与 422 拒绝不可写字段的既有安全语义。
|
||
|
||
## 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
|
||
```
|
||
|
||
适合配置加载、后台代码、服务内部更新等受控路径。
|
||
|
||
`readable(false)`、`sensitive()`、`required()` 和 `include_default(false)` 是独立的消费/呈现策略,不改变 `.read_write()`、`.editable()`、`.creatable()` 建立的 intrinsic writable 事实。Gallery 的 Field Policy Matrix 直接从 Descriptor 类型生成这些策略,前端不维护第二份字段能力表。
|
||
|
||
### 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<Model>` 负责。
|
||
|
||
```text
|
||
prepare
|
||
commit
|
||
rollback
|
||
```
|
||
|
||
它可以代表配置文件、设备、远端服务等外部动作。
|
||
|
||
### 14.3 数据库事务
|
||
|
||
由数据库自身负责。
|
||
|
||
数据库 transaction 不能替代进程内对象锁;对象 mutex 也不能替代数据库 ACID。
|
||
|
||
三者可以在一次业务流程中协作,但职责必须保持独立。
|
||
|
||
## 15. 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 的中心算法。
|
||
|
||
## 16. 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”更符合低侵入原则。
|
||
|
||
## 17. Frontend 应保持被动
|
||
|
||
Adminive 是 backend-driven 系统,因此前端不应该重新维护:
|
||
|
||
```text
|
||
字段名称
|
||
业务枚举
|
||
显示条件
|
||
校验规则
|
||
CRUD schema
|
||
配置树结构
|
||
```
|
||
|
||
前端的职责是:
|
||
|
||
```text
|
||
获取后端描述
|
||
按普通组合节点执行后端布局语义
|
||
对 frontend 节点自行决定最终定位
|
||
渲染页面
|
||
提交用户输入
|
||
展示后端错误
|
||
```
|
||
|
||
如果一个业务字段修改后必须同时修改 C++ descriptor 和 TypeScript hard-coded schema,说明 backend-driven 边界已经被破坏。
|
||
|
||
前端可以拥有纯 UI 行为,但不应该成为第二份业务 Schema。
|
||
|
||
### 17.1 Capability Case Registry
|
||
|
||
示例画廊不能维护一份和真实实现脱离的 capability 字符串。每个可宣称的能力必须对应一个 Case,Case 至少绑定:
|
||
|
||
```text
|
||
id / group / title / summary
|
||
C++ declaration
|
||
View or Manifest JSON
|
||
runtime or renderer JSON
|
||
preview
|
||
endpoints
|
||
tags
|
||
```
|
||
|
||
总览能力矩阵、活文档和 endpoint 索引从 Case Registry 派生。这样“文档说支持”必须同时能指向协议、运行时入口或自动测试。新增能力如果没有 Case,就不应该自动出现在完整能力清单中。
|
||
|
||
Collection 能力实验台也遵守这个原则:paging/sort/search/filter/row reorder/status 发送真实 HTTP;column reorder 属于前端表现状态,因此只消费 `column_reorderable` 授权,不伪造后端业务请求。协议边界页直接重复执行非法 Composition、Manifest、readonly、validation、polymorphic 和 Collection query,而不是复制预先写好的错误文字。
|
||
|
||
## 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 的 frontend/group/card/grid/flow/list/tabs 组合
|
||
Table View 的列选择与顺序
|
||
Collection View 的 table/list/cards mode、item 语义和独立 CRUD id/reload 目标
|
||
Form/Collection submit payload 只包含 View 中可 editable/creatable 的字段
|
||
sortable/searchable/filterable/fixed 的后端能力约束
|
||
Composition View 的 slot、frontend 与跨组件组合
|
||
Composition Manifest 的 Slot Contract 完整性
|
||
/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
|
||
Value Adapter
|
||
Control Adapter
|
||
Polymorphic Adapter
|
||
HTTP Adapter
|
||
AMIS Adapter
|
||
```
|
||
|
||
### 23.5 Transaction contract
|
||
|
||
```text
|
||
prepare failure
|
||
runtime commit failure
|
||
external commit failure
|
||
rollback
|
||
snapshot consistency
|
||
Config Store persistence
|
||
```
|
||
|
||
这些测试保护的是设计边界,而不仅是当前实现细节。
|
||
|
||
### 23.6 Release-safe assertion contract
|
||
|
||
测试断言不能依赖标准 `assert()`。Release 默认定义 `NDEBUG`,如果测试仍使用 `assert()`,CTest 通过并不能证明表达式被执行。Adminive 测试统一使用始终生效的 `ADMINIVE_CHECK`;`static_assert` 继续用于编译期契约。旧同步 API 的测试文件不保留兼容实现,有价值的并发、nested、unsynchronized 和 callback lifetime 用例迁移到当前 `Managed_Value` 测试。
|
||
|
||
### 23.7 Transport 与安装黑盒测试
|
||
|
||
Service 单元测试不能替代 transport 黑盒测试。Httplib adapter test 必须启动真实 localhost server,再通过 client 请求 descriptor/view/data/amis、Resource Context、Collection CRUD/query/reorder/status 和错误状态。Drogon 保持独立 adapter contract test。
|
||
|
||
安装测试必须先执行 `cmake --install` 到构建目录内的固定 prefix,再从独立 consumer 工程只通过 `find_package(Adminive)` 配置和运行。测试 consumer 不允许使用源码 include path,否则无法发现 export/install 错误。
|
||
|
||
### 23.8 Verification Gate
|
||
|
||
发布前 Gate 至少包含:
|
||
|
||
```text
|
||
Debug full build + CTest
|
||
Release full build + CTest
|
||
all public headers standalone include compilation
|
||
package ZIP structure verification
|
||
Core + Default/Httplib install consumers
|
||
ASan + UBSan on Clang/GNU
|
||
TSan concurrency tests on Clang/GNU
|
||
ASan on MSVC
|
||
frontend unit tests
|
||
frontend production build
|
||
Playwright Chromium E2E
|
||
Playwright Edge E2E on Windows
|
||
real HTTP integration
|
||
```
|
||
|
||
CTest label 用于按 `unit/protocol/http/install/package/header/concurrency/frontend/e2e` 分层定位失败。Clang/GNU 的 ASan+UBSan 对 Core Adapter、Advanced Adapter、Safety、Managed、View Schema、Drogon 与真实 Httplib 运行 sanitizer 矩阵,TSan 单独覆盖并发 Managed 路径;MSVC 只运行真实 ASan,配置不支持的 UBSan/TSan 必须直接失败而不能假通过。公共头必须逐个独立 include 编译,安装 consumer 同时验证 Core 与 Default/Httplib,源码 ZIP 必须实际生成并检查精确根文件、旧实现残留和嵌套工程。Sanitizer 是测试配置,不改变 Core API。frontend E2E 验证同一 Manifest 五种布局、Form modes、Collection CRUD/query/reorder/status、422 nested validation、Sensitive 输出过滤、Object Adapter transaction counter、transaction rollback、状态序列、协议错误和缓存策略。Gate 使用确定的目录和端口,不依赖随机构建路径。
|
||
|
||
## 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 后续演进时最需要守住的架构边界。
|