接口:不同对象的共同形状
定义 Schedulable,给三种订单做属性映射,按接口一次查询、汇总三种类型。

本课目标
读完这一课,你将能够
- 说出接口解决什么问题,以及它和对象类型有什么不同
- 定义
Schedulable,把三种订单映射到它上面 - 按接口一次查询、汇总多个对象类型,并避开同名不同义的坑
三种订单,一个问题
前两节讲的是对象和对象之间的连线。这一节换一种「连接」:把形状相同的类型放在一起用。示例工厂里有三种带到期日的东西:销售订单要在承诺日期前发货,工单要在计划时间前完工,采购订单要在预计到货日前到货。计划员每天早上却问同一个问题:这两周哪些快到期了?
没有接口,只有两条路:写三遍查询、三遍汇总;或者把三种订单塞进一个大对象类型,让一半属性永远为空。接口(Interface)是第三条路:它只描述形状,也就是有哪些属性、哪些关系、哪些动作,让形状相同的对象类型去实现。之后按接口查,三种订单一起出来。
| 对象类型 | 接口 | |
|---|---|---|
| 是什么 | 具体的,背后是数据 | 抽象的,只有形状 |
| 有对象吗 | 有,可以有很多个 | 没有,对象总属于某个具体类型 |
| 值从哪来 | 数据源或 Action | 实现它的对象类型 |
什么时候值得抽接口?三次法则:两个类型相像,先观察;第三个类型又重复同样的属性和查询,就该抽了。将来出现第四种可排期的类型,让它实现同一个接口,已有的查询不用改。
定义接口,再做属性映射
在 AIDC 里,接口的属性通常由共享属性承载(也可以直接写在接口上,下一节会看到),所以先建 dueAt 和 status 两个共享属性,再建接口。每个接口属性可以标必填或可选:必填的,实现方一定要映射。
[
{ "kind": "sharedPropertyType", "apiName": "dueAt", "title": "Due date", "schema": { "type": "date" } },
{ "kind": "sharedPropertyType", "apiName": "status", "title": "Status", "schema": { "type": "string" } },
{
"kind": "interfaceType",
"apiName": "Schedulable",
"title": "Schedulable",
"schema": {
"properties": {
"dueAt": { "required": true },
"status": { "required": true }
}
}
}
]
必填还是可选,要想清楚。必填是对每个实现方的硬要求,接口用得越广,越难收紧;可选属性可以先不映射,方便一个类型一个类型地接入。给已有实现方的接口加必填属性,或者把可选改成必填,会被标为破坏性变更。所以新属性先写成可选,等实现方都补齐再收紧。
实现方在自己的定义里写 implements:接口属性名指向自己的属性名,名字相同的可以省略。下面是三个对象类型各自 schema.implements 的内容。
{
"salesOrder": [{ "interface": "Schedulable", "properties": { "dueAt": "promisedDate" } }],
"purchaseOrder": [{ "interface": "Schedulable", "properties": { "dueAt": "expectedAt" } }],
"workOrder": [{ "interface": "Schedulable", "properties": { "dueAt": "dueDate" } }]
}
这里有一条硬规则:必填的接口属性必须映射到类型相同的属性。示例工厂里有个现成的坑:销售订单的 promisedDate 和采购订单的 expectedAt 是 date,工单的 dueAt 却是 timestamp。直接让工单实现接口,定义会被拒:
workOrder.dueAt: type timestamp differs from Schedulable.dueAt (date)
办法是给工单加一个 date 类型的 dueDate,映射到接口的 dueAt,也就是上面代码里的写法。「快到期」按天算就够了,工单原来的 dueAt 不动。不要图省事把类型硬改掉:改属性类型是破坏性变更,会牵连所有用到它的应用。
按接口查询与汇总
实现之后,接口就是一个对象集的入口(interfaceBase),SDK 里写 client.interface("Schedulable")。属性用接口的名字,__apiName 告诉你每个对象是哪个具体类型。
const soon = await client.interface("Schedulable")
.where({ dueAt: { $lt: "2026-10-13" } })
.fetchPage({ $orderBy: { dueAt: "asc" }, $pageSize: 50 });
// each object carries __apiName: salesOrder, workOrder or purchaseOrder
const byStatus = await client.interface("Schedulable")
.aggregate({ $select: { $count: "unordered" }, $groupBy: { status: "exact" } });
Postgres 方言只为符合条件的 Interface 生成视图。Interface 要有属性,名称不能超过 63 字节,且至少一个实现它的 Object Type 有视图。视图用 UNION ALL 合并有视图的实现类型,并增加 __objectType 和 __primaryKey 两列。
SELECT "__objectType", "__primaryKey", "dueAt", "status"
FROM "Schedulable"
WHERE "dueAt" < '2026-10-13'
ORDER BY "dueAt";
一个容易踩的坑:接口只统一了名字,没有统一含义。三种订单的 status 取值不一样,订单有 draft、confirmed,工单有 planned、released。按接口过滤「未完成」时,不要假设取值相同;需要的话,另加一个含义统一的属性,比如布尔的 isOpen,再映射进接口。
下一节给接口加三样东西:继承、关系,以及接口上的 Action。
要点
- 接口只描述形状,没有自己的对象;对象类型实现它,之后按接口查就能跨类型。
- 三个类型开始重复同样的属性和查询时,就该抽接口。
- 必填的接口属性必须映射到类型相同的属性;类型不同,先解决类型,别硬改。
- 入口是
interfaceBase对象集(SDK 的client.interface());Semantic 数据库里的接口视图把实现方合在一起。 - 接口统一名字,不统一含义:同名属性的取值不同时要小心。
练一练
把三种订单接到 Schedulable 上
在自己的 ontology/ 目录里做。
写出 Schedulable 的三份定义(两个共享属性加接口),再给 purchaseOrder 写 implements。status 为什么可以不写映射?
让 workOrder(dueAt 是 timestamp)直接实现,运行 aidc semantic define ontology/ --dry-run,读到类型不一致的报错;加上 dueDate 再跑一次。
写出「未来两周内到期、按类型汇总个数」的接口查询,SDK 或 SQL 任选。SQL 里怎么按类型分组?
小测
选一个答案,马上看解析。
Q1Schedulable 接口里存着哪些数据?
接口是抽象的,没有自己的对象。按接口读到的,就是实现它的对象类型里的当前值。
Q2工单的 dueAt 是 timestamp,接口的 dueAt 是 date。直接映射会怎样?
类型在定义时就校验,不会拖到查询时。办法是加一个 date 类型的属性去映射,不要硬改原属性的类型。
Q3什么时候最值得抽出一个接口?
三次法则:两个像是巧合,三个重复就该重构。为只有一个实现的接口花力气,是过早抽象。