Ontology 即 API:本体模型比 REST API 更适合做契约
每个做过大型平台的工程师都经历过这样的噩梦:
Ontology 即 API:本体模型比 REST API 更适合做契约
“系列:S10 设计模式 · 第 1 篇 | 难度:高级 | 阅读时间:18 分钟
#TL;DR
- 传统 REST API 以"端点"为中心,每新增一个业务场景就要加一组端点,最终导致端点爆炸和版本地狱。
- Ontology-as-API 模式将本体模型作为契约层,所有操作都作用于"对象-关系-属性"三元组,而非硬编码的端点路径。
- coomia-dip 通过 Ontology Schema 自动生成 gRPC 服务、SDK 客户端和权限策略,实现一次建模、处处可用。
#引言:API 设计的困境
每个做过大型平台的工程师都经历过这样的噩梦:
/api/v1/users
/api/v1/users/{id}/orders
/api/v1/users/{id}/orders/{orderId}/items
/api/v2/users/{id}/orders/{orderId}/items # v2 加了折扣字段
/api/v1/users/{id}/recommendations # 新需求
/api/v1/users/{id}/risk-score # 又一个新需求
每来一个业务需求,就要加一个端点。每改一个字段,就要出一个新版本。你以为你在做 API 设计,实际上你在做端点运维。
这还只是一个微服务。当你有 20 个微服务、每个微服务 30 个端点、3 个版本并存时,你面对的是 1800 个端点。没有任何文档能跟上这个速度,没有任何团队能保证它们的一致性。
问题的根源不在于 REST 不好,而在于以"端点"为中心的契约模型天然不适合复杂的业务语义。
#一、REST API 的三大结构性缺陷
#1.1 端点与业务逻辑的耦合
REST API 的设计哲学是"资源+动词"。这在简单 CRUD 场景下工作得很好:
GET /users → 列出用户
POST /users → 创建用户
GET /users/{id} → 获取用户
PUT /users/{id} → 更新用户
DELETE /users/{id} → 删除用户
但现实业务不是 CRUD。当你需要"将用户从部门 A 调到部门 B,同时更新其权限、通知其主管、记录审计日志"时,你该调哪个端点?
PUT /users/{id}/department?to=B # 方案 1:多步调用
POST /users/{id}/transfer # 方案 2:自定义动作
POST /operations/user-transfer # 方案 3:操作中心
三种方案都是妥协。REST 的"资源+动词"模型根本没有表达业务操作的能力。你不得不在标准动词之外发明新的端点来表达业务语义,这就是为什么 REST API 最终总会退化成 RPC-over-HTTP。
#1.2 跨实体查询的表达力不足
业务分析师经常问这样的问题:"找出所有在过去 30 天下过订单、且风险评分高于 80 的用户,按其所在部门分组,显示每个部门的平均客单价。"
用 REST API 回答这个问题,你需要多次调用不同端点再在客户端聚合,这就是 N+1 查询问题。GraphQL 部分解决了这个问题,但引入了查询解析、深度限制、缓存失效等新复杂性。
#1.3 版本管理的无底洞
当你在响应中添加一个字段,所有消费者都必须处理这个变化。你面临破坏性变更、版本爆炸、或复杂度转嫁三个不理想的选择。coomia-dip 面对的是 8 个 Layer、63 个 proto 文件、数百个服务的系统。如果走传统 REST 路线,版本管理本身就需要一个团队。
#二、Ontology-as-API 模式的核心思想
#2.1 核心洞察
“API 的契约不应该是端点列表,而应该是业务对象模型。
- 传统 REST:契约 = URL 路径 + 请求/响应 JSON Schema
- Ontology-as-API:契约 = 对象类型 + 属性定义 + 关系定义 + 操作定义
在这个模式下,你不再设计端点,而是设计本体模型:
ObjectType: TransferOrder
Properties:
- sourceWarehouse: Link<Warehouse>
- targetWarehouse: Link<Warehouse>
- items: List<Link<InventoryItem>>
- status: Enum<Draft, Pending, Approved, Executed, Cancelled>
Actions:
- approve(approver: User, comment: String) -> TransferOrder
- execute(executor: User) -> TransferOrder
- cancel(reason: String) -> TransferOrder
所有平台能力——查询、变更、订阅、权限——都围绕这个模型自动生成。
#2.2 从模型到 API 的自动推导
在 coomia-dip 中,一旦定义了 Ontology Schema,以下内容会自动生成:
ObjectType 定义
|
v
gRPC Service -> 类型化的 CRUD + Action 接口
Python SDK -> client.TransferOrder.get(id)
TypeScript SDK -> await client.TransferOrder.get(id)
Permission Rules -> 基于对象类型的 ABAC 策略
Subscription -> 对象变更的实时通知
Audit Trail -> 所有操作自动记录
关键在于,生成的 API 天然理解业务语义。当你在 TransferOrder 上定义了 approve Action,系统知道这是一个需要权限检查、会触发状态变迁、需要记录审计日志的业务操作。
#2.3 与 Palantir Foundry 的对比
Palantir Foundry 是 Ontology-as-API 模式的先驱。coomia-dip 在其基础上做了三个改进:
- 开源透明:Schema 的定义和推导逻辑完全开源
- gRPC 内核:所有内部通信基于 gRPC,性能优于 Foundry 的 REST 内核
- 多语言 SDK:自动生成 Python、TypeScript、Java 三种语言的类型化 SDK
#三、coomia-dip 中的实现架构
#3.1 Schema Registry:元数据中心
Control Layer(Control Layer)中的 Schema Registry 负责存储和管理所有 Ontology Schema 定义。它维护完整的类型关系图,知道哪些类型之间有关系、哪些属性是派生的、哪些 Action 会触发级联操作。
Schema Registry
|-- ObjectTypes: User, Department, Order, Product, ...
|-- RelationTypes: User->Department, User->Order, ...
|-- Property Schemas
|-- Action Definitions
|
v
API Gateway -> 根据 Schema 动态路由
SDK Generator -> 根据 Schema 生成类型化客户端
Permission -> 根据 Schema 生成 ABAC 策略
#3.2 gRPC 服务的自动生成
当新 ObjectType 注册时,Control Layer 自动生成对应的 gRPC 服务定义:
service TransferOrderService {
rpc Get(GetTransferOrderRequest) returns (TransferOrder);
rpc List(ListTransferOrdersRequest) returns (ListTransferOrdersResponse);
rpc Create(CreateTransferOrderRequest) returns (TransferOrder);
rpc Update(UpdateTransferOrderRequest) returns (TransferOrder);
rpc Delete(DeleteTransferOrderRequest) returns (Empty);
rpc Approve(ApproveTransferOrderRequest) returns (TransferOrder);
rpc Execute(ExecuteTransferOrderRequest) returns (TransferOrder);
rpc Cancel(CancelTransferOrderRequest) returns (TransferOrder);
rpc Subscribe(SubscribeTransferOrderRequest) returns (stream TransferOrderEvent);
rpc GetRelated(GetRelatedRequest) returns (GetRelatedResponse);
}
关键点:Actions 是一等公民、订阅内置、关系查询内置。
#3.3 SDK 客户端的类型安全
from ontology_sdk import OntoPlatform
platform = OntoPlatform(endpoint="grpc://control-Layer:9090")
# 类型安全的对象操作
order = platform.objects.TransferOrder.get("order-123")
print(order.source_warehouse.name)
# 类型安全的 Action 调用
approved_order = order.approve(approver=current_user, comment="批准调拨")
# 类型安全的查询
high_value_orders = (
platform.objects.TransferOrder
.where(lambda o: o.total_value > 100000)
.where(lambda o: o.status == "Pending")
.order_by(lambda o: o.created_at, desc=True)
.limit(20)
.list()
)
每个方法调用都有完整的类型提示,IDE 自动补全,mypy 编译时检查。
#3.4 权限的自动绑定
权限是 Schema 的一部分,而非手动绑定:
ObjectType: TransferOrder
Actions:
approve:
permissions:
- role: WarehouseManager
condition: object.sourceWarehouse.manager == caller
- role: SupplyChainDirector
condition: always
权限检查在 gRPC 接口被调用时自动发生,从根本上消除"忘了加权限检查"的安全漏洞。
#四、解决 REST 的三大缺陷
#4.1 告别端点爆炸
新增业务能力 = 在 Schema 中添加 ObjectType 或 Action。系统自动生成所有接口。从 10 个实体类型增长到 100 个时,管理的是 100 个 Schema 定义,而非 3000 个端点。
#4.2 告别 N+1 查询
跨实体查询在 Ontology-as-API 中是一等公民。查询会被翻译成优化过的执行计划,Ontology 层知道实体间关系,自动选择最优查询路径。
#4.3 告别版本地狱
Schema 版本变更可被自动分析为"向后兼容"或"破坏性变更"。向后兼容的变更自动部署,破坏性变更需要显式迁移计划。
#五、实践指南
#5.1 从领域模型开始
传统:业务需求 -> API 设计 -> 实现 -> 文档
OaaA:业务需求 -> 领域建模 -> Ontology Schema -> 自动生成一切
设计原则:派生属性自动计算、状态机定义合法转换、关系是双向的。
#5.2 Action 设计最佳实践
- 表达业务意图,而非技术操作
- 原子性——要么全部成功,要么全部回滚
- 声明副作用——通知、审计、工作流自动触发
#5.3 Schema 演进策略
- 添加属性:总是安全的
- 删除属性:先标记废弃,到期删除
- 修改关系:使用迁移工具验证和回滚
#六、性能考量
#6.1 三级缓存
L1: 进程内缓存(HashMap, TTL=60s)
L2: 分布式缓存(Redis, TTL=5min)
L3: Schema Registry(PostgreSQL)
Schema 变更通过事件总线广播失效通知。
#6.2 查询优化
查询优化器根据数据分布和索引情况选择最优执行计划——可能是 SQL JOIN,也可能是图遍历。
#6.3 与 gRPC 协同
Protobuf 与 Ontology Schema 一一对应、Server Streaming 支持订阅、二进制序列化比 JSON 小 3-10 倍。
#七、与其他模式的协同
| 模式 | 协同方式 |
|---|---|
| 状态机模式(S10-05) | Action 状态转换定义在 Schema 中 |
| 级联模式(S10-08) | 派生属性依赖在 Schema 中声明 |
| 查询重写(S10-09) | 权限规则从 Schema 推导 |
| 契约驱动(S10-12) | Schema 是 proto 文件的上游 |
| 多租户(S10-14) | 租户隔离策略在 Schema 中定义 |
#八、反模式与陷阱
#8.1 过度建模
不是所有数据都需要进入 Ontology。判断标准:业务用户需要直接操作的 -> ObjectType,只有开发者在代码中使用的 -> 内部数据结构。
#8.2 万物皆 Action
简单属性修改用标准 Update。Action 保留给有业务语义的操作。
#8.3 忽视关系设计
关系是 Ontology 的灵魂。ObjectType 之间应该有丰富的关系定义。
#九、实战案例:供应链决策平台
一家制造企业的供应链决策平台:
ObjectTypes:
Supplier:
properties: { name: String, rating: Decimal, leadTime: Duration }
derived:
onTimeRate: Decimal = orders.count(onTime) / orders.count()
Warehouse:
properties: { name: String, location: GeoPoint, capacity: Integer }
derived:
utilizationRate: Decimal = inventory.sum(quantity) / capacity
criticalItems: List<InventoryItem> = inventory.filter(needsReorder)
actions:
requestRestock: { items, urgency } -> List<PurchaseOrder>
InventoryItem:
properties: { quantity: Integer, reorderPoint: Integer }
derived:
needsReorder: Boolean = quantity <= reorderPoint
SDK 使用:
platform = OntoPlatform(endpoint="grpc://control-Layer:9090")
critical = platform.objects.Warehouse.where(
lambda w: w.critical_items.count() > 0
).list()
for wh in critical:
wh.request_restock(items=wh.critical_items, urgency="Urgent")
零 REST 调用,全部类型安全,自动权限检查。
#十、总结
Ontology-as-API 是 coomia-dip 最核心的设计决策:
- 端点爆炸 -> Schema 定义替代端点列表
- N+1 查询 -> 关系感知的查询引擎
- 版本地狱 -> Schema 演进替代 API 版本管理
你不再设计 API,你设计业务模型。API 是模型的投影。
#参考资料
- Palantir Foundry Ontology 文档↗
- coomia-dip 架构总览
- gRPC 官方文档↗
- Eric Evans, Domain-Driven Design, Addison-Wesley, 2003
下一篇:S10-02 策略路由模式