十 · 技术栈分层与选型框架
L0–L5 六层模型与决策树
选型的困难不在「哪个项目好」,而在「我现在缺的是哪一层」。这一章把语义栈拆成六层,每层给出职责、候选、自建还是采购的判据、以及最常见的失败方式;然后给三种典型团队的完整架构。
六层模型
| 层 | 职责 | 候选 | 建议 |
|---|---|---|---|
| L0 数据 | 源系统,权威值的所在 | 核心、ERP、CRM、湖仓 | 不自建,只能接 |
| L1 存储 | 三元组/图/属性图的持久化与查询 | rdflib、Jena+TDB2、Oxigraph、Neo4j、TerminusDB | 按查询形态选,不按「是不是图」选 |
| L2 模型 | 语义的单一源与多目标编译 | LinkML、Protégé(分析用)、YAML+Turtle | 必须自建,这是你唯一的资产 |
| L3 校验与推理 | 拒绝坏数据;推出隐含事实 | pySHACL、SHACL-AF、OWL-RL、ELK、Reasonable | 校验必买/必用,推理按需 |
| L4 语义服务 | 查询、API、虚拟化、检索 | Fuseki、Ontop、GraphQL、向量库 | 按消费方式选 |
| L5 消费 | BI、Agent、应用、动作 | 自建 | 必须自建,这是价值所在 |
三层关键判断先说结论:
- L0 与 L1 是「别人的地盘」,你的技术决策只影响接入成本,不影响成败。
- L2 与 L3 是「你的护城河」,这里的投入直接决定三年后这份资产是能用还是能删。
- L5 是「唯一能被业务看见的层」,所有其他层的价值都要在这一层兑现,否则等于零。
L0:数据层
这一层看起来没什么可选的——源系统就在那里。但有两个决定会长期影响上层:
一、权威值是单源还是多源。 同一个客户名在两个系统里都说是权威,这个矛盾不会在上层消失,只会被推到上层并放大。做法:给每个关键字段标注权威源,冲突时以权威源为准,差异进对账队列(细节见第六章的状态所有权三分法)。
二、主键是否稳定。 如果源系统的主键会变(重组、迁移、重新编号),那么上层所有引用都会断。做法:在 L0 与 L1 之间加一层「永久 ID 映射」,永久的那个 ID 由你治理。
L1:存储层
选型的实际问题是三个,不是一个:
问题一:需要事务与持久化吗? 不需要 → rdflib(内存)。需要 → Jena TDB2、Oxigraph、图库。
问题二:查询形态是什么? 以「多跳路径 + 聚合」为主 → 属性图(Cypher 更省事)。以「模式匹配 + 联邦」为主 → SPARQL。以「时序回溯」为主 → 需要版本化存储(TerminusDB、或自己在属性图上加双时态)。
问题三:需要推理吗? 需要 → 优先选支持推理或能挂推理机的 RDF 存储。不需要 → 属性图几乎总是更省事。
三个不该犯的错:
- 用内存图当生产库。 rdflib 没有优化器,也没有并发控制,它适合脚本与测试。
- 把「图」当成「语义」。 图数据库给你的是存储与查询,不是类型与约束。
- 忽略许可。 三家常被推荐的内存图库里,两家用的是源码可用许可。
L2:模型层——唯一必须自建的资产
这一层的产物不是软件,是你自己的语义。所以要选的不是「哪个工具最好」,而是「用什么格式承载它,十年后还能读」。
三个要求,按重要性排序:
- 人可读、可 diff。 纯文本,能进 git,能在 PR 里 review。
- 可编译。 一份源能生成 JSON Schema、SQL DDL、OWL、SHACL、文档。
- 不绑定工具。 换一个编辑器不需要重写模型。
符合这三条的现实选择只有两类:YAML 系的建模语言(LinkML 一类,自带 41 个生成器模块)与Turtle 手写 + 脚本生成(成本更高,但完全可控)。
一个反直觉的建议:Protégé 不要当源,当分析工具。 它在检查分类是否合理、公理是否矛盾上无可替代,但它的工程文件不可 diff、不可编译、不可被其他工具消费。正确用法是「导入模型 → 分析 → 把结论写回源文件」。
L3:校验与推理——一个必用,一个慎用
校验必用。 没有校验的语义层只是文档。要求三条:可执行、有非零退出码、在写入或合并路径上。pySHACL 是最省事的选择:覆盖 SHACL Core + SPARQL + Advanced Features,退出码明确。
推理慎用。 它不报错、耗时不可预测、诊断信息对业务方无意义(第四、十章有实测)。合理的位置是构建期(检查建模错误)、或查询期(OBDA 下推),不要放在写入路径。
一个必须知道的能力边界:OWL 2 RL 推不出「至少一个」这类约束,而它恰恰是业务里最常见的约束。这类需求归校验层,不归推理层。
L4:语义服务层
按消费方式分四种,选一种就够,别混:
| 消费方式 | 形态 | 工具 |
|---|---|---|
| 人查 | SPARQL 端点 + 可视化查询器 | Fuseki、Sparnatural |
| 应用调 | REST/GraphQL API | 自建薄层,别直接暴露 SPARQL |
| 不搬数据 | OBDA 虚拟化 | Ontop(+ R2RML 映射) |
| 模型检索 | 向量 + 图混合 | 图库 + 向量索引 |
第三条(OBDA)在第九章被判为「最被低估的一条」,理由很实际:它把「建语义层」从「重建数据资产」降级为「写一份映射」。代价是映射错了不报错,所以必须配对账。
L5:消费层——价值兑现的地方
这一层没有现成工具,因为它就是你的业务。它决定前面四层是资产还是负债。
两条设计原则:
一、先做一个能演示的闭环,再做平台。 一个闭环 = 一类对象 + 五条约束 + 一次可拒绝的动作 + 一张能对账的报表。这个闭环能在两周内做出来,并且能让业务方当场看到「坏数据被挡住了」。
二、动作要在这一层,规则要在 L2/L3。 动作的声明(参数、前置条件、副作用、权限)是 L5 的事,但前置条件的实现必须落到 L3 的约束上。否则规则会分裂成两套:一套在模型里、一套在代码里,然后慢慢不一致。
三种典型架构
A. 小团队(1–3 人,一个业务域,两周出结果)
L0 关系库(不动)
L1 不引入图库 —— 直接用关系库 + 视图
L2 YAML 模型(十几类) ← 唯一新建
L3 pySHACL(对导出数据跑)
L4 不建端点 —— 生成 SQL 视图供 BI 用
L5 一张对账报表 + 一份违规清单
要点:不引入新的存储与端点。语义层以「生成的 SQL 视图 + 校验流水线」形态存在,业务方无感知,但口径统一了。这是投入产出比最高的形态。
B. 中型(一个平台组,3–8 人,多业务域)
L0 多个源系统 + 永久 ID 映射
L1 三元组库(Jena TDB2 或 Oxigraph)或属性图
L2 LinkML 一类编译器 → OWL + SHACL + DDL + 文档
L3 CI 里跑 SHACL + 一致性检查;构建期跑 ELK
L4 Fuseki(内部查询)+ REST API(应用)
L5 一个业务应用 + 一个 Agent 上下文接口
要点:L2 的编译器是核心投资。它让「改模型」变成「改 YAML 提 PR」,而不是「找人改图」。
C. 受监管(有审计要求,10 人以上)
L0 源系统 + 双写对账
L1 带版本的存储(TerminusDB 或自建双时态)
L2 编译器 + 变更影响面计算
L3 SHACL(三档严重级别 + 带期限豁免)+ 策略版本化
L4 端点 + 动作网关(所有写入经动作)
L5 审计台(who/what/when/why/result 五字段)+ 对账任务
要点:写入必须经动作,动作必须留五个字段的审计,审计必须能回答「当时生效的策略是什么」。这三件事任缺一件,架构 C 就退化成 B。
三件不该做的事
不要先建全量本体。 「先把整个企业的概念梳理清楚再动手」是这类项目最常见的死法。正确顺序是反的:先用一个小域跑通闭环,再抽公共部分。
不要为推理而推理。 如果没有一个具体的、业务真的会问的「能不能推出」问题,推理层就是纯成本。
不要在建校验之前建可视化。 漂亮的图会让人以为工作完成了,而它通常是唯一没有下游消费者的产物。
成本的一个粗估
按人力月计(不含硬件):
| 形态 | 首版 | 稳态维护 | 主要成本项 |
|---|---|---|---|
| A 小团队 | 0.5–1 人月 | 0.2 人月/月 | 模型 + 校验 |
| B 中型 | 3–5 人月 | 1 人月/月 | 编译器 + CI + 端点 |
| C 受监管 | 8–15 人月 | 2–3 人月/月 | 动作网关 + 审计 + 对账 |
稳态维护成本是最容易被低估的一项,也是决定项目能否活过第二年的关键。如果拿不出「每月一人月」的维护预算,就不要做 B 或 C——做 A,并且接受它的能力上限。