业务故事站
P2 Foundry

本体建模:把业务表变成对象

本体(Ontology)是 Foundry 对业务数据的"翻译层":把 orders 这类原始表,变成业务人员能看懂的 order 对象。本主题 5 个故事覆盖:从表到对象的首次建模、多团队语义统一、Action 动作设计、N:N 占位链接的坑,以及对象设计评审会上的影响分析与删除拦截。

数据工程师 业务分析师 本体建模 属性约束 Action 设计 链接关系 共 5 个故事

能 / 不能速览

✅ 这个主题能做
  • 把表建成"对象":配主键(单列或复合)、属性映射、数据类型(text/number/date/bool/enum)与约束(enum/range/unique)
  • 给属性加显示名、同义词、PII 标记,统一团队语义并自动联动列级安全
  • 设计带参数 Schema 校验 + SQL 回写模板 + 乐观锁的 Action,形成唯一受控写入口
  • 用链接(1:N / N:N)表达对象间关系;N:N 通过 join_dataset 映射 join 表
  • 对象定义导出 / 导入 YAML(apiVersion: zy-foundry/ontology/v1),删除前先做影响分析
⛔ 这个主题做不了
  • 对象名必须是合法 api_name(小写字母开头的小写字母/数字/下划线),中文名只能放显示名
  • N:N 链接必须有真实的 join 表;demo 里 order_items 只是占位,查不到"某订单买了什么"
  • 被下游引用(链接 / 指标 / 管道 / 血缘)的对象禁止删除,必须先把依赖清干净
  • 属性数据类型只有 5 种,自定义复合类型、向量类型暂不支持

适用角色

本主题面向两个核心角色:

  • 数据工程师:建模主力,掌握对象 / 属性 / 链接 / Action 的定义与约束,是核心使用者。
  • 业务分析师:确认语义口径(显示名、同义词、约束),消费对象与指标。

数据源负责人保证表结构就绪;管理员负责对象级权限与删除审批。

能力速览(能做什么)

对象建模

把表映射为对象:主键(pk_column / 复合 pk_columns)、属性映射列、数据类型与约束,校验引擎在创建/更新前拦截非法定义。

属性语义

显示名、同义词、PII 标记、enum / range / unique 约束,把"客户、区域"这类跨团队口径统一钉在属性定义上。

Action 设计

param_schema 类型化参数 + write_back_config(SQL 模板 / 乐观锁 / 幂等键字段),形成唯一受控写入口。

链接关系

1:N / N:N 表达对象间关系;N:N 必须配 join_dataset 映射 join 表(source_key / target_key)。

YAML 导入导出

对象定义导出为声明式 YAML(两遍扫描导入,对象级失败不整体回滚),可进 git 评审、跨环境复用。

调整指南(怎么调整)

  • 改属性口径:给 region 加同义词"区域,大区"提升语义检索命中率;给 name 加 PII 标记,列级安全自动生效;给 status 配 enum 约束。
  • 改主键:单主键用 pk_column;复合主键用 pk_columns(如 ["tenant_id","id"]),主键列须稳定唯一且非空。
  • 改 Action:改 param_schema 的 required / enum 控制入参;写回模板用命名占位符 :param;需要审批就把 requires_approval 置 true。
  • 改关系:1:N 用 source_property / target_property 关联;N:N 必须先准备真实 join 表再配 join_dataset。
  • 改复用方式:导出 YAML 后导入即可复刻对象定义;导入走两遍扫描,单个对象失败只记 Skipped。

做得好的场景

本体建模把"物理表 → 业务对象"一步到位,特别适合以下场景:
  • 快速建模上线:十几分钟把表建成对象,替代以前 3~7 天的 SQL + 接口排期。
  • 跨部门语义统一:约束、同义词、PII 标记全部钉在属性上,口径对不上的问题大幅减少。
  • 受控写入口:Action 把"改什么、怎么改、改了留痕"收进唯一入口,杜绝裸 UPDATE。
  • 声明式交付:YAML 导入导出让对象定义可评审、可迁移、可复用。

限制与不足

以下是明确的边界,使用前先知道:
  • 对象名限英文:api_name 必须是合法标识符(小写字母开头的小写字母/数字/下划线),中文只能放显示名。
  • N:N 依赖真实 join 表:demo 的 order_items 是占位映射,当前查不到"某订单买了哪些商品"明细。
  • 被引用对象禁删:指标 / 管道 / 血缘 / 进行中编辑会阻塞删除,须先清理依赖。
  • 数据类型有限:只有 text / number / date / bool / enum 五种,复合类型与向量类型暂不支持。
  • demo 数据重启重建:源表改动会被还原,平台元数据与编辑态则保留。

场景故事

故事 1 数据工程师第一次建模:把 customers 表建成 customer 对象
背景
张工是刚接手电商数据平台的"数据工程师",他的第一件事是把 foundry_demo_warehouse(SQLite 演示数据源,启动自动重建)里的 customers 表建成 customer 对象,让不懂 SQL 的业务同事以后能自助查"客户在哪个区域"。
传统做法对比
以前要人先看 ER 图、写建表 DDL,再让 IT 排期开发查询接口,快则 3~7 天;现在打开"本体工作台"填一张表单,10 分钟建完,5 分钟就能在对象查询里看到数据。
角色
数据工程师(拥有本体建模与数据源管理权限);对象建好后业务分析师可直接消费。
操作步骤
  1. 登录平台(admin / admin1,端口 18081 或经 5173 的 /api 前缀)
  2. 侧边栏"数据源"确认 foundry_demo_warehouse 已注册并导入元数据
  3. 打开"本体工作台",点击"新建对象"
  4. 填写 api_name=customer、显示名=客户,主表 customers、主键 customer_id
  5. 映射 customer_id / name / region 三个属性后保存
系统响应
创建成功返回结构示例:
{
  "api_name": "customer",
  "display_name": "客户",
  "base_table": "customers",
  "pk_column": "customer_id",
  "properties": ["customer_id", "name", "region"],
  "version": 1
}
侧边栏对象列表立即出现 customer。
结果洞察
对象查询里选 customer 直接返回 3 条客户记录(张三·华东、李四·华北、王五·华南)。属性映射一旦建好,查询引擎自动按映射列拼 SQL,业务人员不用再关心表结构。注意:demo 环境 seed 已预置同名对象,重复创建会返回 DUPLICATE_ENTRY。
调整建议
给 region 加同义词(如"区域、大区")方便语义检索;给 name 打 PII 标记联动列级安全;映射列名与源表列核对一致,避免查询为空。
动手试一试
登录:http://127.0.0.1:18081(或 Vite 5173),账号 admin / admin1。页面路径:数据源 → 本体工作台 → 新建对象。输入内容:api_name=customer、主表 customers、主键 customer_id、属性 customer_id/name/region。预期结果:创建成功,对象查询选 customer 返回 3 条客户记录。
限制提示
对象名只能是合法 api_name(小写字母开头的小写字母/数字/下划线),中文名放显示名;demo 数据源每次启动重建,临时改动重启后会被还原。
故事 2 多团队对"客户"定义分歧:用约束、同义词、PII 统一语义
背景
周三下午,张工拉了 CRM、财务、客服三个团队的接口人开会对齐"客户"的定义——CRM 叫"客户"、财务叫"往来单位"、客服叫"会员";同一个 region 有的写"华东"有的写"HUADONG",同名不同义的字段满天飞,报表口径天天对不上。
传统做法对比
以前靠一份共享 Excel 词典加微信群对齐,来回 1~2 周,还总有人用错;现在把语义约束直接钉在属性定义上,用的人看不到错误选项,错数据根本进不来。
角色
数据工程师(在属性定义上配置约束与标记);业务分析师(确认口径与业务叫法)。
操作步骤
  1. 打开 customer 对象 → 属性管理
  2. 给 region 加同义词"区域,大区",给 name 打 PII 标记(is_pii=true)
  3. 给 status 属性配 enum 约束:shipped / cancelled / pending
  4. 给 customer_id 加 unique 约束,防止重复客户
  5. 保存后到"语义检索"输入"大区"验证命中
系统响应
属性保存后结构示例:
{
  "name": "region",
  "display_name": "地区",
  "data_type": "text",
  "synonyms": "区域,大区",
  "is_pii": false
}
语义检索输入"大区"时命中 property region,matched_fields 含 property_synonyms,score 0.7。
结果洞察
同义词让团队黑话能检索到属性;PII 标记自动联动列级安全(查询时按权限过滤);enum / unique 约束在写路径与查询层兜底,坏数据进不来,"区域写 HUADONG"这种事当场被拦。
调整建议
约束可再叠加 range(金额区间)与计算属性(is_calculated + formula);新增字段前先查一遍现有属性避免重名;enum 枚举值增删后记得同步业务手册。
动手试一试
页面路径:本体工作台 → customer → 属性 region。输入内容:synonyms 填"区域,大区"后保存。预期结果:语义检索搜"区域"或"大区"能命中属性 region。
限制提示
约束在平台写路径与查询层生效,demo 源表本身没有数据库级约束,直连库写入仍可能绕过;属性数据类型只有 text / number / date / bool / enum 五种。
故事 3 设计 update_order_status 动作:参数校验 + SQL 模板 + 乐观锁
背景
订单状态要允许业务改,但不能让业务直接碰库。张工在 order 对象上设计一个 update_order_status 动作:只有它能改订单状态,且带乐观锁防止覆盖别人的修改,每次执行都要留痕。
传统做法对比
以前要么找 DBA 手写 UPDATE(无审计无回滚,出了事查不到谁改的),要么走线下 OA 审批再人工同步,一次状态变更动辄半天。现在把"改什么、怎么改、改了留痕"全部收进 Action 一个入口。
角色
数据工程师(设计动作定义);运营人员(执行,需 action:update_order_status 权限点)。
操作步骤
  1. 在 order 对象 → 动作管理 → 新建动作
  2. 填 param_schema:order_id(number,必填)、status(string,enum shipped/cancelled/pending,必填)、expected_updated_at(string)
  3. 配 write_back_config:sql_template 为 UPDATE orders SET status=:status, updated_at=strftime('%Y-%m-%d %H:%M:%f','now') WHERE order_id=:order_id AND updated_at=:expected_updated_at,optimistic_lock=true
  4. 填 edit_type=modify、idempotency_key_field=order_id
  5. 保存
系统响应
保存成功后动作定义写入 ontology_actions,Action 测试页出现 update_order_status。执行时自动走五步安全流水线:参数 Schema 校验 → 逐动作 RBAC → 记录级检查 → 属性级写权限三分 → 审计 + 幂等 + 乐观锁回写 + 编辑态落盘。执行响应示例(HTTP 200 ≠ 成功,成功看 status / validation_result):
{
  "mode": "VALIDATE_AND_EXECUTE",
  "status": "success",
  "validation_result": { "result": "VALID" },
  "edits": { "changed_records": 1 },
  "rows_affected": 1,
  "operation_id": "run_7"
}
结果洞察
业务只能通过这个动作改状态,无法绕过;expected_updated_at 不匹配时返回 409 ACTION_CONFLICT(附 latest_state),不会静默覆盖别人改过的行;每次执行都进审计日志与血缘图,追责有据。改完后对象查询会叠加编辑态返回值(编辑态永久优先)——改完立即可查,不依赖源表是否同步成功。
调整建议
需要审批就把 requires_approval 置 true(提交评审时自动要求至少 1 人过审且不可自我批准);要防重放就保证调用方每次传一致的 idempotency_key;批量改先 VALIDATE 再执行。
动手试一试
页面路径:本体工作台 → order → 动作管理,或直接看 seed 预置的 update_order_status。输入内容:Action 测试填 order_id=2、status=shipped、expected_updated_at=查询到的 updated_at。预期结果:先 VALIDATE 返回 success,再执行返回 rows_affected=1。
限制提示
写路径支持 SQL 类回写的四种编辑类型(create / modify / delete / link 均有执行器),HTTP / API 类对接外部系统未实现;modify 的 sql_template 必须带 WHERE,否则记录级检查直接整批拒绝;编辑态读叠加仅行级对象查询生效,指标聚合查询仍按源表计算。
故事 4 建 order_to_product 多对多链接,遇到占位 join 的限制与应对
背景
张工要给 order 和 product 建多对多关系(一张订单含多个商品、一个商品出现在多张订单)。但 demo 数据源里只有 orders 和 products 两张表,没有中间表 order_items,他先按占位方式把关系定义建起来。
传统做法对比
数据库里多对多必须建中间表、加外键、写 join,数据团队排期 3~5 天;平台允许先用 join_dataset 声明映射关系,业务口径先定下来、明细后补。
角色
数据工程师(建链接定义);数据源负责人(后续补 order_items 表)。
操作步骤
  1. 打开 order 对象 → 链接管理 → 新建链接
  2. 填 name=order_to_product、目标对象 product、基数 N:N、方向 directed
  3. 配 join_dataset:join_table=order_items、source_key=order_id、target_key=product_id
  4. 保存
系统响应
链接创建成功(N:N 校验要求必须提供 join_dataset)。但到"对象查询"里用 order_to_product.name 取商品名时,因为 order_items 表在 demo 数据源里并不存在,查询会报错或返回空——这就是占位 join 的真实边界。
结果洞察
链接定义先落库,业务语义(订单 ↔ 商品)已表达清楚;但查询明细必须等数据团队把 order_items 表建好并导入元数据,再把 join_dataset 指向真实表后才可用。
调整建议
短期用两个 1:N 链接(order → order_item、order_item → product)搭桥过渡;长期补表后更新 join_dataset;上线前先做影响分析,看谁引用了这个占位链接。
动手试一试
页面路径:本体工作台 → order → 链接管理。输入内容:name=order_to_product、目标 product、N:N、join_table=order_items。预期结果:链接创建成功;对象查询里查 order_to_product.name 预期为空或报错,理解占位 join 行为。
限制提示
N:N 必须配 join_dataset 且 join 表需真实存在并导入元数据;demo 的 order_items 仅为占位,当前查不到"某订单买了哪些商品"明细。
故事 5 对象设计评审会:删除被引用对象被拦,影响分析兜底
背景
周五评审会,张工提议废弃旧 order 对象、改用新的 sales_order 双轨过渡。王姐提醒:order 是销售指标(total_gmv、aov)的底座,直接动会出大事。张工现场演示"影响分析"和"删除",给评审一个直观交代。
传统做法对比
以前删表前靠人肉问一圈"谁在用",漏了某个报表就线上事故,修复要 1~2 天;现在平台在删除前自动做影响分析,把依赖清单拍在桌面上,删不删一目了然。
角色
数据工程师(演示影响分析与删除);评审人 / 管理员(拍板,指标负责人确认依赖)。
操作步骤
  1. 打开 order 对象 → 点击"影响分析"
  2. 查看返回的引用清单:链接、动作、指标、进行中编辑
  3. 再尝试删除 order,观察拦截
  4. 评审决定:不删,改为新建 sales_order 走版本草稿合入
系统响应
影响分析返回:
{
  "object_type_id": 2,
  "object_type_name": "order",
  "impacts": [
    {"type": "metric", "name": "total_gmv", "detail": "列 entity_id 引用对象 id=2"},
    {"type": "metric", "name": "aov", "detail": "列 entity_id 引用对象 id=2"},
    {"type": "action", "name": "update_order_status", "detail": "action belongs to object type"}
  ]
}
删除时被下游引用阻塞,返回错误而非硬删。
结果洞察
被下游引用的对象受平台保护,杜绝"删完才发现报表挂了";影响清单让评审有据可依,核心对象一律先走版本流程再改。
调整建议
把影响分析纳入每次合入 / 删除的前置动作;对核心对象配置审批策略;被引用属性想删就先清理指标 / 管道依赖。
动手试一试
页面路径:本体工作台 → order → 影响分析。输入内容:无需输入,直接查看。预期结果:返回 metric total_gmv / aov、action update_order_status 等依赖清单。
限制提示
对象删除是软删(status=deprecated);指标 / 管道 / 血缘 / 进行中编辑会阻塞删除,先清依赖再删;影响分析对未建表的方向(pipeline_definitions / data_lineage)会注明"表未就绪"。

常见问题

本体和数据库表有什么区别?

表是物理存储,本体是业务语义层:属性映射、约束、同义词、PII 标记、链接、Action 全部挂在对象上。业务查询走语义层,不直接面对表结构。

api_name 为什么必须英文小写?

它是稳定标识(合法格式:小写字母开头的小写字母/数字/下划线),供 YAML、契约、权限点 action:<name> 引用;显示名才用于界面展示,可以写中文。对象还带稳定标识 rid(object-type:<name>,由 name 确定性推导),对象路由支持按 api_name / rid 定位,跨环境 / 重建后外部引用不失效。

对象能带复合主键吗?

可以。单主键用 pk_column,复合主键用 pk_columns(如 ["tenant_id","id"])。主键属性需稳定唯一、非空、不可变。

属性映射列填错了会怎样?

查询按映射列拼 SQL,映射列与源表列不一致会导致查询为空或报错;创建对象前先在数据源里核对列名。

N:N 链接什么时候才能真正用起来?

join 表真实存在并导入元数据后,把 join_dataset 指向真实表即可。demo 的 order_items 只是占位,暂时查不到明细。

主题小结

一句话:本体建模把"数据表的物理结构"翻译成"业务的语义对象"。记住四件事:对象名用英文 api_name;属性用约束 / 同义词 / PII 统一语义;写回只走 Action;N:N 必须依赖真实 join 表。建模之前,先想清楚主键、口径和依赖关系。