三 · 建模语言与约束选型
OWL / SHACL / LinkML / 属性图 / 对象类型
选型问题通常被问成「OWL 还是属性图」,但这是个伪问题。真正要选的是:你要表达的约束落在哪一层。这一章先给四层表达模型,再给语言对照表与决策树。
四层表达模型
任何语义模型要表达的东西,可以按「越往下越难执行」排成四层:
| 层 | 要表达的东西 | 典型手段 | 可执行性 |
|---|---|---|---|
| L1 结构 | 有哪些字段、什么类型、必填还是可选 | JSON Schema、SQL DDL、LinkML | 强:几乎所有工具都能校验 |
| L2 约束 | 跨字段条件、枚举、基数、值域 | SHACL、ShEx、检查约束 | 强:可在写入路径执行 |
| L3 语义 | 子类、等价、传递、逆、定义域值域 | OWL 2(EL/QL/RL/DL) | 中:需要推理机,且不报错 |
| L4 动作 | 前置条件、副作用、权限、回滚 | 动作 DSL、策略引擎 | 弱:没有标准,各家自造 |
这个分层解释了一个反复出现的现象:大多数「本体项目」实际只做到了 L1 和 L2,却用 L3 的语言在描述。 用 OWL 写出来的东西,如果只被当作文档和 schema 使用,那它的表达力是浪费的,而它的工具链成本是照付的。
反过来也成立:如果一个需求真的需要 L3(例如「有心脏的动物都是脊椎动物」这种可推导的分类),用 JSON Schema 是做不到的——你只能把结论手工写进数据,然后承担它随时间漂移的代价。
语言对照
| 语言 | 定位 | 表达力 | 工具成熟度 | 典型代价 |
|---|---|---|---|---|
| OWL 2 EL | 大规模分类(SNOMED 级) | 存在与合取、部分角色限制 | 高(ELK) | 表达力窄,写不出「至多一个」 |
| OWL 2 RL | 规则可实现的子集 | 可用前向链规则实现 | 中(OWL-RL、Reasonable) | 只有 maxCardinality 0/1,表达力被规则集限制 |
| OWL 2 QL | 查询重写(OBDA) | 可用 SQL 重写实现 | 中(Ontop) | 为下推牺牲表达力 |
| OWL 2 DL | 完整表达 | 最强 | 低且在萎缩(Pellet 停更) | 推理不可预测,解释困难 |
| SHACL | 数据形状校验 | 闭合世界、强校验 | 高(pySHACL、TopBraid) | 不做推理,只判对错 |
| ShEx | 类似的形状语言 | 与 SHACL 接近 | 中 | 生态比 SHACL 小 |
| LinkML | 建模语言 + 编译器 | L1/L2 为主,可编译到 OWL | 中高(41 个生成器) | 不是运行时,是构建期 |
| JSON Schema | 接口契约 | L1 | 极高 | 表达不了图结构与跨对象约束 |
| 属性图(Cypher/GQL) | 存储与查询 | L1 + 部分 L4 | 高 | 无标准语义,约束靠应用层 |
| DTDL | 数字孪生 | L1 + 遥测语义 | 中(Azure 生态) | 绑生态 |
几条值得单独说的判断:
OWL 2 RL 的实际价值被高估了。 它常被当作「不装推理机也能推一点」的折中,但它的规则集只支持 maxCardinality 为 0 或 1 的基数限制,minCardinality 根本推不出来。如果你的需求里有「至少一个负责人」这类约束,OWL 2 RL 帮不上忙——那是 SHACL 的活。
SHACL 是这一代真正的赢家。 原因是它闭合世界、可执行、退出码明确。它不产生新知识,但它能挡住坏数据,而挡住坏数据是业务能感知的价值。第十一章会看到,这个判断有实测支撑。
LinkML 解决的是另一个问题:谁是源。 它本身不是运行时语言,而是「一份 YAML 编译到多个目标」的构建期工具。它的价值不在表达力,在于让 schema 变成可 review 的文本。
属性图不是「更差的 RDF」。 它在路径查询、存储效率、运维熟悉度上占优;它缺的是标准语义与推理。选择取决于你需要 L3 还是不需要——不需要 L3 时,属性图几乎总是更省事。
决策树
从需求出发,按顺序判断:
- 只需要接口契约(字段、类型、必填)? → JSON Schema。不要引入本体工具链。
- 需要跨字段条件、枚举、基数,并且要拒绝坏数据? → SHACL(RDF 栈)或检查约束(关系库)。判断依据是你的数据是否天然是图:多对多、层级深、关系本身带属性。
- 需要「从已有事实推出新事实」? → 看规模: 十万级以上类、只需分类 → OWL 2 EL + ELK 规则形态清晰、可用 Datalog 表达 → OWL 2 RL 或直接规则引擎 需要完整 DL 表达 → 先问「谁来维护解释」,多数情况下答案是「没人」,那就退回 EL
- 需要「表不动、语义在上层」? → OBDA(虚拟知识图)。R2RML / R2RML 方言的映射,SPARQL 重写成 SQL。
- 需要动作与权限? → 没有标准。自建 DSL,把动作声明成数据(参数、前置条件、副作用、权限),并把前置条件实现为第 2 步的约束。
- 需要一份源、多个下游格式? → LinkML 一类编译器,或自己写生成器。
一个被忽略的选型维度:谁改它
表达力之外,还有一个维度决定成败:变更的日常执行者是谁。
- 如果改模型的人是数据工程师,用 YAML + git,他会很舒服;给他 Protégé,他会绕着走。
- 如果改模型的人是领域专家,用表格或可视化编辑器;给他 Turtle,他会在两周内放弃。
- 如果两边都要改,就必须有一个「生成物」和一个「源」,且明确哪边不许手改。
这一点比表达力更能预测项目存活率。推断:选型时把「变更执行者」放在第一位,把表达力放在第二位,能显著降低「建完就没人维护」的概率。
反模式:用上层语言写下层需求
最常见的浪费是把 L2 的需求用 L3 的语言写:
- 需求其实是「这个字段必填、这个枚举有五个值」
- 实现却用 OWL 的基数限制与枚举类
- 结果:需要推理机才能校验,推理机不报错,校验实际没生效
正确的做法是同一个模型里分层:结构用 L1、约束用 L2 并真的执行、只在确实需要推导的地方用 L3,且明确标注「这一段有推理依赖」。这样即使推理机被下线,前两层仍然有效。