返回博客

Ontology 即 API:本体模型比 REST API 更适合做契约

每个做过大型平台的工程师都经历过这样的噩梦:

Coomia发布于 2025年12月15日11 分钟阅读
分享本文Twitter / X

Ontology 即 API:本体模型比 REST API 更适合做契约

系列:S10 设计模式 · 第 1 篇 | 难度:高级 | 阅读时间:18 分钟

#TL;DR

  • 传统 REST API 以"端点"为中心,每新增一个业务场景就要加一组端点,最终导致端点爆炸和版本地狱。
  • Ontology-as-API 模式将本体模型作为契约层,所有操作都作用于"对象-关系-属性"三元组,而非硬编码的端点路径。
  • coomia-dip 通过 Ontology Schema 自动生成 gRPC 服务、SDK 客户端和权限策略,实现一次建模、处处可用

#引言:API 设计的困境

每个做过大型平台的工程师都经历过这样的噩梦:

Code
/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 场景下工作得很好:

Code
GET    /users       → 列出用户
POST   /users       → 创建用户
GET    /users/{id}  → 获取用户
PUT    /users/{id}  → 更新用户
DELETE /users/{id}  → 删除用户

但现实业务不是 CRUD。当你需要"将用户从部门 A 调到部门 B,同时更新其权限、通知其主管、记录审计日志"时,你该调哪个端点?

Code
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:契约 = 对象类型 + 属性定义 + 关系定义 + 操作定义

在这个模式下,你不再设计端点,而是设计本体模型

YAML
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,以下内容会自动生成:

Code
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 在其基础上做了三个改进:

  1. 开源透明:Schema 的定义和推导逻辑完全开源
  2. gRPC 内核:所有内部通信基于 gRPC,性能优于 Foundry 的 REST 内核
  3. 多语言 SDK:自动生成 Python、TypeScript、Java 三种语言的类型化 SDK

#三、coomia-dip 中的实现架构

#3.1 Schema Registry:元数据中心

Control Layer(Control Layer)中的 Schema Registry 负责存储和管理所有 Ontology Schema 定义。它维护完整的类型关系图,知道哪些类型之间有关系、哪些属性是派生的、哪些 Action 会触发级联操作。

Code
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 服务定义:

PROTOBUF
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 客户端的类型安全

Python
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 的一部分,而非手动绑定:

YAML
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 从领域模型开始

Code
传统:业务需求 -> API 设计 -> 实现 -> 文档
OaaA:业务需求 -> 领域建模 -> Ontology Schema -> 自动生成一切

设计原则:派生属性自动计算、状态机定义合法转换、关系是双向的。

#5.2 Action 设计最佳实践

  1. 表达业务意图,而非技术操作
  2. 原子性——要么全部成功,要么全部回滚
  3. 声明副作用——通知、审计、工作流自动触发

#5.3 Schema 演进策略

  • 添加属性:总是安全的
  • 删除属性:先标记废弃,到期删除
  • 修改关系:使用迁移工具验证和回滚

#六、性能考量

#6.1 三级缓存

Code
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 之间应该有丰富的关系定义。

#九、实战案例:供应链决策平台

一家制造企业的供应链决策平台:

YAML
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 使用:

Python
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 最核心的设计决策:

  1. 端点爆炸 -> Schema 定义替代端点列表
  2. N+1 查询 -> 关系感知的查询引擎
  3. 版本地狱 -> Schema 演进替代 API 版本管理

你不再设计 API,你设计业务模型。API 是模型的投影。

#参考资料

  1. Palantir Foundry Ontology 文档
  2. coomia-dip 架构总览
  3. gRPC 官方文档
  4. Eric Evans, Domain-Driven Design, Addison-Wesley, 2003

下一篇:S10-02 策略路由模式