值类型与约束
八种约束、版本与写入时校验;把规则写进类型,而不是写进每个应用。

本课目标
读完这一课,你将能够
- 说出值类型和基础类型的区别,说出八种约束
- 写出一个值类型定义,并用到属性上
- 说清写入时校验和版本对改动的影响
基础类型管形状,值类型管含义
上一节的共享属性,让多个类型共用一份属性定义。这一节的值类型再进一步:让多个属性共用同一条取值规则。
string 只说「这是一段文字」。但订单状态不是随便一段文字,它只能是 draft、confirmed、inProduction、shipped、closed、cancelled 六个值之一。值类型(Value Type)就是给基础类型加上含义和规则:一个基础类型,加上元数据,再加上约束。
规则写进类型,用到它的每个属性和 Action 参数就都会在写入时被检查,不用在每个应用里各写一遍校验。下面定义两个值类型:订单状态,和订单号的格式。
[
{
"kind": "valueType",
"apiName": "orderStatus",
"title": "Order status",
"description": "Where a sales order is in its life.",
"schema": {
"baseType": "string",
"constraints": [
{ "type": "enum", "options": ["draft", "confirmed", "inProduction", "shipped", "closed", "cancelled"] }
],
"version": "1.0.0"
}
},
{
"kind": "valueType",
"apiName": "orderNumber",
"title": "Order number",
"schema": {
"baseType": "string",
"constraints": [ { "type": "regex", "pattern": "SO-[0-9]{4}-[0-9]{4}" } ],
"version": "1.0.0"
}
}
]
属性用 valueType 引用它。salesOrder 的两个属性这样写:
{ "name": "orderNo", "type": "string", "primaryKey": true, "valueType": "orderNumber" }
{ "name": "status", "type": "string", "title": "Status", "valueType": "orderStatus" }
每个应用各自检查订单状态:一个应用放行 Shipped,另一个只认 shipped,数据里同时出现两种写法。
把订单状态写成值类型 orderStatus:所有写入口共用同一份规则,不合规的值进不来。
值类型还能在多个属性之间复用。salesOrder.totalUsd、customer.creditLimitUsd、product.standardCostUsd 都是金额,都不该是负数:写一个 usdAmount(decimal,range 的最小值为 0),三个属性都引用它就行。
八种约束
| 检查什么 | 示例工厂里的用法 | |
|---|---|---|
enum | 只能是给定的几个值,最多 500 个选项 | orderStatus;customerTier(standard、key、strategic) |
range | 最小值和最大值,至少给一个 | 库存量不小于 0 |
length | 字符串的最短和最长长度 | 产品名 1 到 80 个字符 |
regex | 必须匹配的正则;缺省要匹配整个字符串,partialMatch 才允许只匹配一部分 | 订单号 SO-2026-0412 |
uuid | 必须是合法的 UUID | 外部系统给的唯一标识 |
rid | 必须是合法的资源标识 | 存放资源标识的字符串 |
array | 元素个数的下限和上限(最多 10,000),元素是否必须唯一 | 客户标签最多 5 个,不许重复 |
struct | 给结构体的每个字段指定一个值类型 | 联系人的 email 字段套用邮箱值类型 |
两个容易误会的地方。一是 regex 缺省要匹配整个字符串,只有打开 partialMatch,前面多出一个字母的「XSO-2026-0412」才可能混进来,而这多半不是你想要的。二是值类型只看一个值本身:「承诺交期不早于订单日期」要比较两个属性,那是 Action 提交条件的事,不是值类型的活。
一个值类型最多带 10 个约束。它的基础类型只能是 string、integer、long、short、byte、boolean、float、double、decimal、date、timestamp、array、struct;geopoint、vector 这类特殊类型套不了值类型。
版本与写入时校验
值类型有版本号,写在 version 里,缺省是 1.0.0。显示名、说明和 API name 随时能改;基础类型和约束属于这个版本,改约束就要升版本。改动分两种:
- 兼容的改动:给
customerTier加一个选项partner。已有的数据仍然合规,升版本后,用到它的属性和 Action 都按新规则检查。 - 破坏性的改动:删一个选项,或把范围收窄。已有的数据可能不再合规,用到它的地方会出错。已经有人在用时,稳妥的做法是弃用旧的、新建一个,再逐个迁过去。
常犯的错是把经常增减的分类写成 enum。每加一个选项要升一次版本,删一个更是破坏性改动。公司的部门有几十个、年年调整,更适合建成对象类型 department,用关系连起来;customerTier 这种三五个、几乎不变的取值,才适合 enum。
判断的办法是问一句:这个分类一年里会变几次?会变很多次,就别写进类型里。
给已有数据的属性套约束之前,先查一遍现有的值,再试跑:
aidc semantic sql 'SELECT status, count(*) FROM "salesOrder" GROUP BY 1'
aidc semantic define value-types.json --dry-run
aidc semantic value-types
下一节讲派生属性:不存下来,读的时候沿关系算出来的属性。
要点
- 基础类型管形状,值类型管含义:基础类型加元数据加约束,写入时校验。
- 八种约束:
enum、range、length、regex、uuid、rid、array、struct。 - 值类型有版本号;加选项是兼容的改动,删选项、收窄范围是破坏性的。
- 常变的分类建成对象类型,少数几乎不变的取值才用
enum。
练一练
为订单写两个值类型
销售订单有状态和订单号,客户有等级。
写出 orderStatus(六个状态)和 customerTier(standard、key、strategic)两个值类型,再把它们用到属性上。
为订单号 SO-2026-0412 写一个 regex 值类型,用到 salesOrder.orderNo 上。想一想:partialMatch 缺省是关的,为什么这样更安全?
给 status 套上 orderStatus 之前,用 aidc semantic sql 查一遍现有的状态值,看有没有不在六个选项里的。
小测
选一个答案,马上看解析。
Q1订单号必须长成 SO-2026-0412(SO、四位年份、四位序号)。用哪种约束?
格式规则用 regex,缺省要整串匹配。length 只管长度,任何 12 个字符都能通过;enum 只适合有限的几个值。
Q2customerTier 要增加一个选项 partner,已经有很多应用在用它。怎么做最稳妥?
加选项不会让已有数据不合规,升版本即可。删选项或收窄范围才是破坏性的,那时才需要弃用旧的、新建一个。
Q3公司有几十个部门,年年调整。它们该怎么建模?
经常增减、合并的分类,每次都改 enum 会不断制造破坏性改动。建成对象类型,增减一个部门只是多一个或少一个对象。