33 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
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 负责:
哪些字段成为列
列顺序与标题
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
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 后端与前端的最终边界
后端负责:
字段存在与否
字段 intrinsic capability
单字段 presentation
字段属于哪个视图
字段顺序与逻辑分组
form/collection/page 的语义组合
collection 排序、搜索、筛选等后端能力
slot contract 与组件来源
前端负责:
具体 renderer
CSS / theme
响应式断点
frontend 节点的最终组合方式
最终列宽和像素级间距
设备尺寸下的视觉降级
因此核心原则是:
后端默认拥有页面的语义结构;当后端显式使用 frontend 节点时,只交付内容与顺序,由前端拥有该节点的最终定位;所有节点的视觉实现始终属于前端。
这两个模式不能混淆:vertical/horizontal/flow/grid/list/group/card/tabs 表示后端决定组合语义,frontend 表示后端明确放弃该层的布局决定权。前端不能擅自把普通后端组合节点解释成另一种业务结构。
这样既满足“删字段、加字段、改配置只改后端”,又避免后端被某个具体前端框架的 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”。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 是结构承诺
一旦调用:
.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
适合配置加载、后台代码、服务内部更新等受控路径。
readable(false)、sensitive()、required() 和 include_default(false) 是独立的消费/呈现策略,不改变 .read_write()、.editable()、.creatable() 建立的 intrinsic writable 事实。Gallery 的 Field Policy Matrix 直接从 Descriptor 类型生成这些策略,前端不维护第二份字段能力表。
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
配置树结构
前端的职责是:
获取后端描述
按普通组合节点执行后端布局语义
对 frontend 节点自行决定最终定位
渲染页面
提交用户输入
展示后端错误
如果一个业务字段修改后必须同时修改 C++ descriptor 和 TypeScript hard-coded schema,说明 backend-driven 边界已经被破坏。
前端可以拥有纯 UI 行为,但不应该成为第二份业务 Schema。
17.1 Capability Case Registry
示例画廊不能维护一份和真实实现脱离的 capability 字符串。每个可宣称的能力必须对应一个 Case,Case 至少绑定:
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 建议遵守以下规则:
编译期已知
→ 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 层。同一 Core Service 被多个 transport 复用时,transport 只负责把请求完整翻译成 Core 输入;它们不能各自发明更宽松的协议。例如 Collection GET 中除分页/排序保留参数之外的 query 字段必须交给 Collection_Service 统一校验,httplib 与 Drogon 都不能静默丢弃 View Schema 未授权的筛选字段。独立状态资源也应保持 /descriptor、/amis、/data 协议对齐,请求上下文与异常只在 transport 边界处理。
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 的 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
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
Value Adapter
Control Adapter
Polymorphic Adapter
HTTP Adapter
AMIS Adapter
23.5 Transaction contract
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 保持快速 fake contract test,并提供 ADMINIVE_ENABLE_REAL_DROGON_TESTS=ON 的发布 Gate 编译真实 Drogon route/callback/constraint API 和 installed-package consumer。transport 的 context_factory 属于最外层边界,std::exception 与未知异常都必须在这里转换成结构化 500,不能逃出框架 callback。Collection 的客户端 query/JSON/reorder 输入错误显式映射为 400,字段/对象 validation 为 422;status reader 或其他内部 callback 抛出的未分类异常必须是 500,不能因为共用 safe_response 被误报成客户端错误。
安装测试必须先执行 cmake --install 到构建目录内的固定 prefix,再从独立 consumer 工程只通过 find_package(Adminive) 配置和运行。Adminive 源码树可以使用 pinned bundled nlohmann/json、magic_enum、cpp-httplib 便于可复现 build/test,但安装后的 Adapter target 必须通过外部 CMake package target 解析这些依赖,不能把 bundled header 安装到 ${CMAKE_INSTALL_INCLUDEDIR} 污染消费者命名空间。install consumer 用独立 package fixture 验证导出 dependency wiring,同时确认公共 install prefix 不包含 bundled dependency header。Boost.PFR 和 Drogon 始终由外部 package 提供。测试 consumer 不允许直接使用 Adminive 源码 include path,否则无法发现 export/install 错误。
23.8 Verification Gate
发布前 Gate 至少包含:
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
real Drogon compile + installed consumer release gate
third-party notice/license/package-content verification
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. 设计判断清单
在提交一个新设计前,可以快速检查:
[ ] 这是 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 后续演进时最需要守住的架构边界。