本体设计最佳实践
这篇文章讲的是:如何把一个组织"建模"成一套人人(和 AI)都能看懂的数字结构。 下面用 10 个小步骤,把它拆开讲给完全没有基础的人听。
https://www.palantir.com/docs/foundry/ontology/ontology-best-practices/
原始标题:Ontology design: Best practices
一句话速览
本体设计最佳实践:这篇文章讲的是:如何把一个组织"建模"成一套人人(和 AI)都能看懂的数字结构。下面用 10 个小步骤,把它拆开讲给完全没有基础的人听。
先搞清楚:本体(Ontology)到底是什么?
一句话:本体是你所在组织的"语义层",不是又一个数据库。
假设一家医院有挂号系统、检验系统、设备系统、财务系统。每个系统都有自己的表、自己的字段名、自己的主键。
人和人之间要沟通,得先做一轮"翻译":dtLastInspMod 到底是啥?pat_id 和 patient_no 是不是同一个东西?
本体(Ontology)就是在这些系统之上再建一层:不去描述"数据在哪个表里",而是描述 "现实世界里有什么东西、它们之间是什么关系"。
一个好的本体读起来应该是"顺"的——业务人员不用培训就能看懂,AI 智能体也能顺着它自己找答案。 判断标准很简单:如果看这张图的人需要反复问"这列是什么意思",那建模就失败了。
本体由五块积木拼成
先把名词认全,后面的原则才好理解。
Patient(患者)、WorkOrder(工单)、Vessel(船舶)。Inspectable(可被检查的)、Schedulable(可被预约的)。
接口是本体里避免造出一堆奇怪中间类型的关键工具——第 8 步会展开讲。
从一个反面例子讲起:一张 CSV 引发的思考
新手最常犯的错:把一张表原样搬成一个对象。
数据团队拿到一张订单 CSV,很自然地想:建一个叫 OrderData 的对象类型,一列对一个属性,完事。
这就是原文点名的反模式 「Kitchen Sink」(什么都往里塞)。
下面这张表其实描述了 3 个现实实体。请你试试:点击「归属」列的按钮,把它分到正确的实体里。
| CSV 列名 | 示例值 | 归属 |
|---|---|---|
| order_id | A-10023 | 订单 客户 产品 |
| customer_name | 张三 | 订单 客户 产品 |
| customer_email | zhang@x.com | 订单 客户 产品 |
| product_sku | SKU-8812 | 订单 客户 产品 |
| quantity | 3 | 订单 客户 产品 |
为什么差别这么大?
| 问题 | 后果 |
|---|---|
| 模型不直观 | 人和 AI 都无法自然地在其中导航,因为结构和他们对业务的认知对不上。 |
| 与源头硬耦合 | 源系统改一个字段,所有下游应用一起崩——因为本体在"镜像"源结构,而没有做抽象。 |
| 关系丢失 | 被塞进列里的实体(比如订单上的 customer_name)无法被单独链接、搜索和推理。 |
| 难以复用 | 被某一个系统形状塑造出来的对象类型,别的团队很难拿去用。 |
八条速查清单(先混个眼熟)
这是原文的实用检查表,后面四条核心原则会逐一解释其中几条。
四条核心原则
按优先级排序,冲突时高优先级胜出。
| 优先级 | 原则 | 一句话 |
|---|---|---|
| 1 | Domain-driven design 领域驱动设计 | 建模现实世界,而不是源数据。 |
| 2 | Do not repeat yourself 不要重复自己 | 同样的东西造了三次,就该重构了。 |
| 3 | Open for extension, closed for modification 开闭原则 | 保护核心模型,让其他人能扩展它。 |
| 4 | Composition over deep hierarchies 组合优于深层继承 | 用接口做多重继承,保持可插拔。 |
领域驱动设计
Domain-driven design · Model the real world, not the source data.要避开什么
- 对象类型在镜像源系统的数据表,而不是领域实体
- 属性从源列 1:1 照搬,没有任何筛选
- 命名沿用源系统习惯(
dtLastInspMod),而不是业务语言(lastInspectionDate) - 模型是"看着数据"设计出来的,而不是"理解了业务"设计出来的
- 一行源数据里其实包含多个实体,却被建模成单个对象类型
该怎么做
- 先看领域,再看表结构和业务方一起定义"哪些概念重要"。一个数据集往往描述多个实体。
- 区分「身份」与「观测」如果一行数据表示对某实体的一次测量或事件,那实体和观测很可能是两个不同的对象类型。
- 为人命名API 名要直观、自解释。用
person.children而不是person.linkedChildPersonObjects。 - 先建模,再映射数据理解领域 → 设计对象模型 → 把源数据映射进去。不要反过来。
- 非语义类型标记为 hidden纯技术用途的类型设为隐藏,保持默认视图干净,构建应用时仍可用。
不要重复自己(三法则)
Do not repeat yourself · If you built the same thing three times, refactor.
重复的对象类型、冗余的属性、复制粘贴的工作流,不仅是维护负担,更是上下文管理灾难——
无论是人还是 AI 智能体,面对三个长得几乎一样的 Customer,都无法判断哪个才是"正主"。
要避开什么
- 多个对象类型拥有相同的属性和相似的链接
- 同一段派生属性逻辑或动作逻辑散落在多个类型里
- 不同团队为略有差异的用途造出近乎相同的对象类型
- 存在只有细微差别、靠复制粘贴产生的工作流
该怎么做
- 做一次重复审计如果多个类型共享同一形态,评估:合并成一个类型 + 一个区分属性,还是共同实现一个接口。
- 收敛共享逻辑同一段派生属性或动作逻辑若能复用,抽成接口或共享函数。
- 统一团队私有副本合并为单一规范表示,用权限或过滤来满足各自诉求。
- 套用三法则一处重复可接受,两处是警告,三处就该动手重构。
对扩展开放,对修改关闭
Open for extension, closed for modification · Protect core models. Enable builders to extend them.一个对象类型一旦上线、经过实战检验,它的核心结构就应该稳定下来。 别的团队要加新能力,应该是"在旁边加",而不是"进到里面改"。
要避开什么
- 频繁对已确立的对象类型做破坏性改动,波及所有下游应用
- 新需求靠改核心类型来满足,而不是扩展它
- 团队为了满足自己的特殊需求去编辑共享接口或动作
- 为某一团队做扩展时,安全边界意外扩大,影响到其他使用者
该怎么做
- 识别什么是本质确定哪些属性和链接真正属于这个实体的根本,把它们锁定。
- 为扩展而设计建核心类型时就预留空间:可链接的扩展类型、新的接口实现。
- 优先扩展而非修改新增内容先问一句:它该待在核心类型上,还是该放在扩展里(新链接类型 / 新接口实现 / 新属性命名空间)?
- 守住安全边界核心模型要有清晰的权限边界,确保扩展本体不会顺带扩大数据访问范围。
组合优于深层层次结构
Composition over deep hierarchies · Favor multiple inheritance via interfaces. Keep things pluggable.Foundry 的本体支持通过接口实现多重继承。所以一个实体可以从多个"小而专"的抽象里各取所需, 而不必被塞进一条单一继承的链条里。
要避开什么
- 深长的单继承链,子类型存在的唯一目的就是拼凑父类能力
- 出现
SchedulableBuilding、InspectableVehicle这类把两个无关概念硬揉在一起的"组合类型" - 工作流和具体对象类型紧耦合,明明可以面向共享接口来写
- 给实体加一种新能力,就得重构整条继承链
该怎么做
- 围绕"能力 / 角色"设计接口小而专:
Inspectable、Schedulable、Billable、Depreciable。 - 用分类型接口做聚合比如
MilitaryAsset由Aircraft、Vessel、GroundVehicle实现,特别适合下钻式调查。 - 工作流面向接口编程建在
SchedulableResource上的工作流,对体育馆、会议室、车辆全都适用,无需改动。 - 能组合就别继承实体需要多种能力时,实现多个接口,而不是往继承链深处插一层。
务实与权衡:这些是指南,不是法律
现实里有 deadline、有遗留系统、有平台能力限制——理想设计往往不能一步到位。
一页带走
如果只记四句话:
最后别忘了验证:拿真实的业务问题去跑一遍,看人和 AI 智能体能不能顺着本体找到答案。 能找到,设计才算成立。
延伸阅读 · 相关页面
按主题横向跳转,不必顺着目录一篇篇读。
常见问题速答 · FAQ
关于「本体设计最佳实践」,读者最常问的几个问题。