值类型与约束

第 5 课 · 共 7 课 约 8 分钟

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

本课目标

读完这一课,你将能够

  • 说出值类型和基础类型的区别,说出八种约束
  • 写出一个值类型定义,并用到属性上
  • 说清写入时校验和版本对改动的影响

基础类型管形状,值类型管含义

上一节的共享属性,让多个类型共用一份属性定义。这一节的值类型再进一步:让多个属性共用同一条取值规则。

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)两个值类型,再把它们用到属性上。

小测

选一个答案,马上看解析。

Q1订单号必须长成 SO-2026-0412(SO、四位年份、四位序号)。用哪种约束?

Q2customerTier 要增加一个选项 partner,已经有很多应用在用它。怎么做最稳妥?

Q3公司有几十个部门,年年调整。它们该怎么建模?

延伸阅读