# 场景推演:一次「新增订单」如何被本体模型驱动执行 本文用 `SCENE_CREATE_ORDER` 这一具体场景,逐步推演"架构引擎如何解释与执行模型",说明整体运行原理。 --- ## 0. 一图看懂 ``` models/*.yaml (唯一事实源) ┌───────────────────────────────────────────────────────────┐ │ M0 校验 M1 领域 M2 命令 ME 事件 M3 部署 M4 场景 │ │ M5 安全 M6 监控 M7 SLA MetaRule 规则 M8 前端模板 │ └───────────────────────────────────────────────────────────┘ │(启动/热重载加载) │(GET /api/meta/scene) ▼ ▼ ┌───────────────────────┐ ┌──────────────────────────┐ │ 后端 解释引擎(Java) │◀──REST──│ 前端 渲染引擎(React) │ │ ModelRepository │ │ SceneForm/FieldControl │ │ CommandEngine (通用) │ │ 读 M8 动态生成「新增订单」│ └───────────────────────┘ └──────────────────────────┘ ``` **关键**:没有 `OrderController`、没有 `OrderService.createOrder()` 这类专用代码。`CommandEngine` 对任何场景都走同一套解释流程;`SceneForm` 对任何模板都走同一套渲染流程。 --- ## 1. 页面从哪来:M8 → 合成 Schema → React 渲染 前端请求 `GET /api/meta/scene/SCENE_CREATE_ORDER`。`SceneSchemaService` 做**跨层合成**: 1. 取 M4 场景 `SCENE_CREATE_ORDER` → 得 `flowSteps`、`permissionBind:[order_create]`、绑定命令 `OrderAggregate.CreateOrder`。 2. 取 M8 中 `bindSceneId == SCENE_CREATE_ORDER` 的模板 `template_order_master_detail`,逐组件**富化**: - 用 **M1** 补 `fieldType`(如 `totalAmount → decimal`、`quantity → int`)→ 前端据此选控件。 - 用 **M5** 补 `masked`(`receiverPhone/contactPhone` 打脱敏标记)。 - 对绑定到某聚合根标识的 `select`(`customerId→CustomerAggregate`、`productId→ProductAggregate`)补 `optionsSource`(下拉数据源端点)。 3. 附 **M2** 命令入参/校验、**M3** 命令 API 地址(`/api/v1/order/create`)、**MetaRule** 绑定本场景的规则、以及溯源信息。 前端 `SceneForm` 拿到后按模板结构渲染: - `card` + `aggregate_root` → **主信息卡片**(订单主字段) - `table` + `aggregate_entity(OrderItem)` → **可增删行的明细表格** - `card` + `aggregate_entity(PaymentTerm/DeliveryAddressEntity)` → **单行子实体卡片** - 每个叶子组件按 `compType` 经 `FieldControl` 注册表映射到 antd 控件。 > 因此:改 M8 的 `compType/formLabel/span/childComponents` → 页面即变;这就是"仅维护模板、不改前端源码"。 --- ## 2. 提交做什么:payload → 通用命令执行 用户填完点「提交」,前端按 M8 绑定把值组装为**领域结构**: ```json { "master": { "customerId":"C002", "totalCurrency":"CNY", "totalAmount":7997, "createTime":"..." }, "details": { "OrderItem": [ { "productId":"P001", "quantity":1, "itemPrice":6999, "itemCurrency":"CNY" } ], "PaymentTerm": [ { "paymentType":"月结", "dueDays":30 } ], "DeliveryAddressEntity": [ { "province":"广东", ... , "receiverPhone":"13800002222" } ] } } ``` POST 到 `/api/scene/SCENE_CREATE_ORDER/execute`。`CommandEngine.execute` **逐 M4 flowSteps 解释**: | flowStep(M4) | 引擎动作 | 依据层 | |---|---|---| | `permission`(隐式) | `order_create` 的 `allowPrincipals` 是否含当前主体 | M5 | | `readOnlyCheck: CustomerAggregate` | 校验 `master.customerId` 存在于客户表 | M4 + M1 + M3 | | `bindCommand: CreateOrder` | 见下方"命令执行" | M2 | | `return` | 汇总结果返回 | M4 | **命令执行(CreateOrder)内部**: 1. **校验**:必填项来自 **M8 `required`** 标记(与前端同源);`收货手机号格式`、`dueDays>=0` 等来自 **M2 `validations`** 文本的解释。 2. **风控(MetaRule / Aviator)**: - 订单级:以 `totalAmount` + 客户 `customerLevel`(查客户表得)评估 `R_ORDER_AMOUNT_LIMIT`: `totalAmount > #{single_order_max_amount} && customerLevel != "VIP"` → 命中则 **REJECT**。 - 逐商品级:以每个商品 `stockNum` 评估 `R_STOCK_LOW_WARN`:`stockNum < #{stock_warn_min}` → **ALERT**(不阻断)。 3. **落库(M3)**:生成 `orderId`;据 `t_order_main` 的 `fieldMap` 写主单;据组合子实体(M1 relations)逐个写 `t_order_item / t_order_payment_term / t_order_address`,并补父外键 `order_id`。**列名、表名全部来自 M3**。 4. **发事件(ME/Outbox)**:据 M2 `emitEvents` 写 `OrderCreated / OrderItemAdded / ...` 到 `t_domain_outbox`;topic 取自 ME(无则按命名派生)。 5. **跨聚合最终一致(ME)**:匹配 `crossAggConsistencyRules` 中 `OrderCreated → ProductAggregate.StockDeduct`,逐商品扣减库存(库存列同样取自 M3)。 页面右侧「执行追踪」会显示真实的逐步日志,例如: ``` M5 permission — 主体 normal_user 通过权限校验 [order_create] M4 readOnlyCheck — 校验 CustomerAggregate 引用存在:通过 M2/M8 validate — 命令 CreateOrder 入参校验通过 MetaRule metaRule — 风控规则评估完成,命中 0 条;REJECT=0,ALERT=0 M3 persist — 写入 Order(O3EQD8P2591) 及 3 条子实体,表结构源自 M3 ME emitEvents — 写入 Outbox 事件 4 条 ME crossConsistency — 触发 StockDeduct 扣减 1 个商品库存 M4 return — 场景执行完成,返回结果 ``` --- ## 3. 推演三条"改模型即改行为" ### (a) 大额拦截:改 MetaRule 把 `single_order_max_amount: 50000 → 100`,热重载。**同一笔** 普通用户 7997 元订单: - 改前:`7997 > 50000` 为假 → 通过; - 改后:`7997 > 100 && level!="VIP"` 为真 → **REJECT**,返回 `普通用户单笔订单不可超过50000元...`。 > 规则只维护一份;前端「提交前预检」调用同一后端 MetaRule,实时给出同样结论。 ### (b) 表单改版:改 M8 把 `input_currency` 的 `compType: input → select`、改 `formLabel`,热重载。「新增订单」页的"结算币种"从文本框变为下拉、标签同步更新——**未动任何 React 代码**。 ### (c) 字段类型:改 M1 把某字段 `type: string → int`,热重载。合成 Schema 的 `fieldType` 变化 → 前端控件从 `Input` 变为 `InputNumber`;如需落库再在 M3 增列,`SchemaInitializer` 于重载时 `ALTER TABLE ADD COLUMN` 自动补列。 --- ## 4. 为什么这套架构成立 - **单一事实源**:字段口径(M1)被前端组件、后端校验、DDL、事件负载共同引用,天然对齐,杜绝前后端不一致。 - **解释而非生成**:引擎在运行时解释模型,故"热重载即变",无需重新编译/发版。 - **通用而非专用**:新增一个业务场景 = 新增一套 M1/M2/M3/M4/M8 描述 + 绑定 MetaRule,无需新增 Controller/Service/React 页面。 - **关注点分层**:业务专家维护 M8 模板与 MetaRule 规则;架构师维护 M0–M7 底座;二者解耦。