返回博客

ObjectType 深度解析:属性类型系统与约束体系

在传统数据库中,你只有 VARCHAR、INT、DECIMAL、TIMESTAMP 等有限类型。当业务需要表达"这个字段只能是 3 个值之一"或"这个字段是一个嵌套的地址结构"时,你不得不在应用层处理。

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

ObjectType 深度解析:属性类型系统与约束体系

系列:S4 本体建模 · 第 2 篇 | 难度:中级 | 阅读时间:18 分钟

#TL;DR

  • coomia-dip 提供 20+ 种属性类型,从基础标量(STRING / INTEGER / DOUBLE)到复合类型(STRUCT / ARRAY / MAP / ENUM),覆盖工业级建模的所有需求。
  • 约束体系支持三个层次:字段级(required / unique / range / pattern)、对象级(跨字段规则)、关系级(外键参照完整性),让数据质量在 Schema 层而非应用层保障。
  • 主键设计有 4 种策略(自然键 / UUID / 复合键 / 序列键),选择不当会导致数据分布倾斜、查询性能下降,本文给出决策矩阵。

#1. 为什么属性类型系统如此重要

在传统数据库中,你只有 VARCHARINTDECIMALTIMESTAMP 等有限类型。当业务需要表达"这个字段只能是 3 个值之一"或"这个字段是一个嵌套的地址结构"时,你不得不在应用层处理。

coomia-dip 的 ObjectType 将类型检查前移到 Schema 层:

Code
传统方式                              coomia-dip 方式
┌─────────────┐                      ┌──────────────────────┐
│  Database    │                      │   SchemaRegistry     │
│  VARCHAR(50) │ → 运行时检查          │   STRING(maxLen=50)  │ → 定义时检查
│  INT         │   应用层验证          │   ENUM(A,B,C)        │   Schema 验证
│  TEXT(JSON)  │   无结构保证          │   STRUCT(Address)    │   结构保证
└─────────────┘                      └──────────────────────┘

#2. 完整属性类型清单

#2.1 标量类型(Scalar Types)

类型说明默认约束存储映射
STRING字符串maxLength: 65535VARCHAR
INTEGER32 位整数min: -2^31, max: 2^31-1INT
LONG64 位整数min: -2^63, max: 2^63-1BIGINT
DOUBLE64 位浮点DOUBLE
DECIMAL精确小数precision: 38, scale: 10DECIMAL
BOOLEAN布尔BOOLEAN
DATE日期format: ISO-8601DATE
TIMESTAMP时间戳timezone: UTCTIMESTAMP
TIME时间format: HH:mm:ssTIME
DURATION时间段format: ISO-8601VARCHAR
YAML
# 标量类型示例
properties:
  name:
    type: STRING
    required: true
    constraints:
      minLength: 1
      maxLength: 200

  price:
    type: DECIMAL
    constraints:
      precision: 10
      scale: 2
      min: 0.00

  createdAt:
    type: TIMESTAMP
    defaultValue: "$now"      # 特殊变量:当前时间

  isActive:
    type: BOOLEAN
    defaultValue: true

#2.2 枚举类型(Enum Type)

YAML
properties:
  status:
    type: ENUM
    enumValues:
      - value: DRAFT
        displayName: "草稿"
        description: "初始状态"
      - value: ACTIVE
        displayName: "激活"
        description: "正在使用"
      - value: DEPRECATED
        displayName: "已弃用"
        description: "计划下线"
      - value: ARCHIVED
        displayName: "已归档"
        description: "不可用"
    defaultValue: DRAFT
    transitions:                    # 可选:状态机约束
      DRAFT: [ACTIVE]
      ACTIVE: [DEPRECATED]
      DEPRECATED: [ARCHIVED]

枚举类型 vs STRING + 应用层校验

Code
STRING + 应用层:                    ENUM 类型:
┌───────────────────┐              ┌───────────────────┐
│ 数据库存 "actve"   │ ← 拼写错误   │ 数据库存 "ACTIVE"  │ ← Schema 验证
│ 数据库存 "Active"  │ ← 大小写不一  │ 前端拿到枚举列表    │ ← 自动 UI
│ 前端硬编码选项列表  │              │ API 自动校验       │ ← 无需手写
│ 每个微服务重复校验  │              │ 状态机可选集成     │ ← 声明式
└───────────────────┘              └───────────────────┘

#2.3 复合类型(Composite Types)

YAML
properties:
  # STRUCT:嵌套结构体
  address:
    type: STRUCT
    structType: Address           # 引用已注册的 StructType

  # ARRAY:数组
  tags:
    type: ARRAY
    itemType: STRING
    constraints:
      maxItems: 20
      uniqueItems: true           # 元素不可重复

  # MAP:键值映射
  metadata:
    type: MAP
    keyType: STRING
    valueType: STRING
    constraints:
      maxEntries: 50

  # REFERENCE:引用其他 ObjectType
  ownerId:
    type: REFERENCE
    targetType: User
    onDelete: SET_NULL            # 级联策略

  # ATTACHMENT:文件附件
  documents:
    type: ARRAY
    itemType: ATTACHMENT
    constraints:
      maxFileSize: "10MB"
      allowedMimeTypes:
        - "application/pdf"
        - "image/*"

#2.4 特殊类型(Special Types)

YAML
properties:
  # GEO_POINT:地理坐标
  location:
    type: GEO_POINT
    constraints:
      srid: 4326                  # WGS84 坐标系

  # GEO_SHAPE:地理形状
  boundary:
    type: GEO_SHAPE
    constraints:
      allowedShapes: [POLYGON, MULTI_POLYGON]

  # VECTOR:向量嵌入
  embedding:
    type: VECTOR
    constraints:
      dimensions: 768             # 向量维度
      similarity: COSINE          # 相似度计算方式

  # TIMESERIES:时序数据引用
  temperatureHistory:
    type: TIMESERIES
    valueType: DOUBLE
    resolution: "1m"              # 1 分钟分辨率

  # EXPRESSION:表达式
  displayLabel:
    type: EXPRESSION
    expression: "concat(name, ' (', code, ')')"

#2.5 类型完整关系图

Code
                         Property Types
                              │
              ┌───────────────┼───────────────┐
              │               │               │
          Scalar          Composite        Special
              │               │               │
    ┌─────────┼─────────┐   ┌─┼─────┐    ┌───┼─────┐
    │    │    │    │     │   │ │     │    │   │     │
  STRING INT LONG DOUBLE BOOL STRUCT ARRAY GEO VECTOR TIMESERIES
  DATE TIMESTAMP DECIMAL      MAP   REFERENCE  EXPRESSION
  TIME DURATION ENUM        ATTACHMENT

#3. 约束体系详解

#3.1 字段级约束(Field-Level Constraints)

YAML
properties:
  email:
    type: STRING
    required: true                    # 非空约束
    unique: true                      # 唯一约束
    immutable: false                  # 创建后可修改
    constraints:
      pattern: "^[\\w.-]+@[\\w.-]+\\.\\w+$"  # 正则约束
      maxLength: 255

  age:
    type: INTEGER
    constraints:
      min: 0                          # 最小值
      max: 200                        # 最大值

  score:
    type: DOUBLE
    constraints:
      min: 0.0
      max: 100.0
      multipleOf: 0.5                 # 必须是 0.5 的倍数

  startDate:
    type: DATE
    constraints:
      futureOnly: true                # 只允许未来日期

约束种类速查表

约束适用类型说明
required所有非空
unique所有标量全局唯一
immutable所有创建后不可变
defaultValue所有默认值
min / max数值、日期范围限制
minLength / maxLengthSTRING长度限制
patternSTRING正则匹配
multipleOf数值倍数约束
futureOnly / pastOnlyDATE, TIMESTAMP时间方向
maxItems / minItemsARRAY数组长度
uniqueItemsARRAY元素唯一
maxEntriesMAP映射条目数
allowedMimeTypesATTACHMENT文件类型白名单
maxFileSizeATTACHMENT文件大小限制

#3.2 对象级约束(Object-Level Constraints)

跨字段的业务规则:

YAML
spec:
  properties:
    startDate:
      type: DATE
    endDate:
      type: DATE
    minQuantity:
      type: INTEGER
    maxQuantity:
      type: INTEGER

  constraints:
    # 跨字段约束
    - name: dateRange
      type: COMPARISON
      expression: "startDate <= endDate"
      message: "结束日期必须晚于开始日期"

    - name: quantityRange
      type: COMPARISON
      expression: "minQuantity <= maxQuantity"
      message: "最大数量必须大于等于最小数量"

    # 条件必填
    - name: conditionalRequired
      type: CONDITIONAL
      when: "status == 'ACTIVE'"
      then:
        required: ["activatedBy", "activatedAt"]
      message: "激活状态下必须填写激活人和激活时间"

    # 互斥约束
    - name: mutualExclusive
      type: MUTUAL_EXCLUSIVE
      fields: ["internalCode", "externalCode"]
      message: "内部编码和外部编码不能同时为空"

    # 自定义规则
    - name: luhnCheck
      type: CUSTOM
      expression: "luhn_check(cardNumber)"
      message: "卡号校验位不正确"

#3.3 关系级约束(Relation-Level Constraints)

YAML
# 参照完整性
relations:
  - name: OrderBelongsToCustomer
    from: Order
    to: Customer
    cardinality: MANY_TO_ONE
    constraints:
      required: true                  # 每个 Order 必须有 Customer
      onDelete: RESTRICT              # 有关联 Order 时不能删 Customer
      onUpdate: CASCADE               # Customer 主键变更时级联更新

# 基数约束
  - name: TeamHasMembers
    from: Team
    to: Employee
    cardinality: ONE_TO_MANY
    constraints:
      minCount: 1                     # 团队至少 1 人
      maxCount: 50                    # 团队最多 50 人

#4. 主键设计策略

#4.1 四种主键策略对比

Code
┌──────────────┬─────────────┬────────────┬────────────┐
│    策略       │   示例       │   优点      │   缺点     │
├──────────────┼─────────────┼────────────┼────────────┤
│ 自然键       │ email       │ 业务可读    │ 可能变更    │
│ Natural Key  │ isbn        │ 无需生成    │ 数据倾斜    │
├──────────────┼─────────────┼────────────┼────────────┤
│ UUID         │ uuid-v4     │ 全局唯一    │ 索引膨胀    │
│              │             │ 无需协调    │ 不可读     │
├──────────────┼─────────────┼────────────┼────────────┤
│ 复合键       │ (tenant,id) │ 天然分区    │ 查询复杂    │
│ Composite    │             │ 多租户友好  │ URL 不友好  │
├──────────────┼─────────────┼────────────┼────────────┤
│ 序列键       │ BIGINT auto │ 紧凑       │ 不可分布式  │
│ Sequence     │             │ 有序       │ 信息泄露    │
└──────────────┴─────────────┴────────────┴────────────┘

#4.2 coomia-dip 推荐:语义化 ID 方案

YAML
spec:
  primaryKey:
    property: equipmentId
    strategy: SEMANTIC_ID
    config:
      prefix: "EQ"                    # 类型前缀
      separator: "-"
      segments:
        - type: PROPERTY
          source: factoryCode         # 来自工厂编码
          length: 3
        - type: SEQUENCE
          length: 6                   # 6 位序列号
      # 生成结果:EQ-BJ1-000001, EQ-SH2-000001

决策矩阵

Code
                           分布式友好
                    低 ◄──────────────► 高
                    │                    │
            高      │  序列键            │  UUID
         性  │      │  (单机快)          │  (分布式)
         能  │      │                    │
            │      │                    │
            低      │  自然键            │  复合键
                    │  (需要稳定)        │  (多租户)
                    │                    │
                    └────────────────────┘

  coomia-dip 推荐:语义化 ID(兼顾可读性和分布式)

#4.3 主键配置示例

Python
from ontology_sdk import ObjectTypeSpec, PrimaryKeyConfig

# 策略 1:UUID(默认,最安全)
order = ObjectTypeSpec(
    name="Order",
    primary_key=PrimaryKeyConfig(
        property="orderId",
        strategy="UUID_V4"
    )
)

# 策略 2:语义化 ID(推荐)
equipment = ObjectTypeSpec(
    name="Equipment",
    primary_key=PrimaryKeyConfig(
        property="equipmentId",
        strategy="SEMANTIC_ID",
        prefix="EQ",
        segments=[
            {"type": "PROPERTY", "source": "factoryCode", "length": 3},
            {"type": "SEQUENCE", "length": 6}
        ]
    )
)

# 策略 3:复合键(多租户场景)
tenant_resource = ObjectTypeSpec(
    name="TenantResource",
    primary_key=PrimaryKeyConfig(
        properties=["tenantId", "resourceId"],
        strategy="COMPOSITE"
    )
)

# 策略 4:自然键(稳定业务标识)
country = ObjectTypeSpec(
    name="Country",
    primary_key=PrimaryKeyConfig(
        property="isoCode",
        strategy="NATURAL",
        immutable=True
    )
)

#5. 属性的高级特性

#5.1 派生属性(Derived Properties)

YAML
properties:
  # 表达式派生
  fullName:
    type: STRING
    derived: true
    expression: "concat(firstName, ' ', lastName)"

  # 聚合派生(跨关系)
  totalOrderAmount:
    type: DECIMAL
    derived: true
    aggregation:
      type: SUM
      relation: CustomerPlacedOrder
      property: amount

  # 条件派生
  riskLevel:
    type: ENUM
    derived: true
    expression: |
      CASE
        WHEN creditScore >= 750 THEN 'LOW'
        WHEN creditScore >= 600 THEN 'MEDIUM'
        ELSE 'HIGH'
      END

#5.2 审计属性(Audit Properties)

YAML
spec:
  audit:
    enabled: true
    properties:
      createdBy:
        type: STRING
        autoPopulate: "$currentUser"
      createdAt:
        type: TIMESTAMP
        autoPopulate: "$now"
      updatedBy:
        type: STRING
        autoPopulate: "$currentUser"
      updatedAt:
        type: TIMESTAMP
        autoPopulate: "$now"
      version:
        type: INTEGER
        autoIncrement: true         # 乐观锁版本号

#5.3 敏感属性(Sensitive Properties)

YAML
properties:
  ssn:
    type: STRING
    sensitive: true
    masking:
      strategy: PARTIAL             # 部分掩码
      visibleChars: 4               # 显示最后 4 位
      maskChar: "*"
      # 显示效果:***-**-1234
    encryption:
      algorithm: AES_256_GCM
      keyId: "key-ssn-001"
    access:
      requiredPermission: "view:pii"

#6. 类型系统的运行时行为

#6.1 类型转换规则

Code
┌─────────────────────────────────────────────────┐
│              自动类型转换矩阵                      │
├──────────┬──────────────────────────────────────┤
│ 源类型    │ 可安全转换到                           │
├──────────┼──────────────────────────────────────┤
│ INTEGER  │ LONG, DOUBLE, DECIMAL, STRING        │
│ LONG     │ DOUBLE, DECIMAL, STRING              │
│ DOUBLE   │ STRING                               │
│ DECIMAL  │ STRING                               │
│ BOOLEAN  │ STRING, INTEGER(0/1)                 │
│ DATE     │ TIMESTAMP, STRING                    │
│ STRING   │ (需显式解析,不自动转换)                 │
└──────────┴──────────────────────────────────────┘

安全转换 = 无信息丢失
不安全转换需要显式 CAST

#6.2 NULL 值处理

YAML
# coomia-dip NULL 语义
properties:
  middleName:
    type: STRING
    required: false               # 允许 NULL
    nullSemantics: ABSENT         # NULL = 值不存在(默认)

  deletedAt:
    type: TIMESTAMP
    required: false
    nullSemantics: NOT_APPLICABLE  # NULL = 不适用(未删除)

  score:
    type: DOUBLE
    required: false
    nullSemantics: UNKNOWN         # NULL = 值未知
    defaultOnNull: 0.0             # 查询时 NULL 当作 0.0

#7. 实际案例:设计一个完整的 ObjectType

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Product
  namespace: ecommerce
  version: "1.0.0"
spec:
  displayName: "商品"
  description: "电商平台的商品实体"

  primaryKey:
    property: productId
    strategy: SEMANTIC_ID
    prefix: "PRD"

  properties:
    productId:
      type: STRING
      required: true
      immutable: true

    name:
      type: STRING
      required: true
      constraints:
        minLength: 1
        maxLength: 500
      searchable: true                # 支持全文搜索

    sku:
      type: STRING
      required: true
      unique: true
      constraints:
        pattern: "^[A-Z]{2}-\\d{6}$"

    category:
      type: ENUM
      enumValues: [ELECTRONICS, CLOTHING, FOOD, BOOKS, OTHER]
      required: true

    price:
      type: DECIMAL
      required: true
      constraints:
        precision: 10
        scale: 2
        min: 0.01

    stock:
      type: INTEGER
      required: true
      constraints:
        min: 0
      defaultValue: 0

    images:
      type: ARRAY
      itemType: ATTACHMENT
      constraints:
        maxItems: 10
        maxFileSize: "5MB"
        allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"]

    specifications:
      type: MAP
      keyType: STRING
      valueType: STRING
      constraints:
        maxEntries: 30

    dimensions:
      type: STRUCT
      structType: PhysicalDimension

    # 派生属性
    avgRating:
      type: DOUBLE
      derived: true
      aggregation:
        type: AVG
        relation: ProductHasReview
        property: rating

    totalSales:
      type: INTEGER
      derived: true
      aggregation:
        type: SUM
        relation: ProductInOrderItem
        property: quantity

    isLowStock:
      type: BOOLEAN
      derived: true
      expression: "stock < 10 AND stock > 0"

    displayLabel:
      type: EXPRESSION
      expression: "concat(name, ' (', sku, ')')"

  constraints:
    - name: pricePositive
      expression: "price > 0"
      message: "商品价格必须大于零"

  audit:
    enabled: true

  interfaces:
    - Searchable
    - Auditable
    - SoftDeletable

  lifecycle: DRAFT

#8. 属性类型选择决策树

Code
需要存储什么类型的数据?
│
├── 单个值
│   ├── 文本 → STRING
│   ├── 整数 → INTEGER (< 2^31) 或 LONG
│   ├── 小数
│   │   ├── 需要精确计算(金额)→ DECIMAL
│   │   └── 近似即可(科学计算)→ DOUBLE
│   ├── 是/否 → BOOLEAN
│   ├── 时间
│   │   ├── 只有日期 → DATE
│   │   ├── 只有时间 → TIME
│   │   ├── 日期+时间 → TIMESTAMP
│   │   └── 时间段 → DURATION
│   ├── 有限选项 → ENUM
│   └── 引用其他实体 → REFERENCE
│
├── 结构化值
│   ├── 固定结构 → STRUCT
│   ├── 列表 → ARRAY
│   └── 键值对 → MAP
│
├── 特殊值
│   ├── 地理位置 → GEO_POINT / GEO_SHAPE
│   ├── 向量嵌入 → VECTOR
│   ├── 时序数据 → TIMESERIES
│   ├── 文件 → ATTACHMENT
│   └── 计算表达式 → EXPRESSION
│
└── 从其他属性计算得到 → 任意类型 + derived: true

#9. 性能考量

#9.1 索引策略

YAML
spec:
  indexes:
    # 单字段索引
    - name: idx_product_sku
      properties: [sku]
      type: UNIQUE

    # 复合索引
    - name: idx_product_category_price
      properties: [category, price]
      type: BTREE

    # 全文搜索索引
    - name: idx_product_name_fts
      properties: [name]
      type: FULLTEXT
      analyzer: "ik_smart"           # 中文分词器

    # 向量索引
    - name: idx_product_embedding
      properties: [embedding]
      type: HNSW
      config:
        m: 16
        efConstruction: 200

#9.2 存储优化建议

场景推荐原因
高基数 STRING考虑 maxLength避免 VARCHAR(MAX)
频繁查询的 ENUM索引 + 物化避免全表扫描
大 ARRAY考虑独立 ObjectType避免文档膨胀
大 MAP考虑独立 ObjectType避免键名爆炸
ATTACHMENT分离存储 (MinIO)避免数据库膨胀
TIMESERIES专用时序表写入优化

#10. 常见错误与最佳实践

#错误 1:过度使用 STRING

YAML
# ❌ 错误
status:
  type: STRING        # 可以存入任何值

# ✅ 正确
status:
  type: ENUM
  enumValues: [ACTIVE, INACTIVE, SUSPENDED]

#错误 2:忽略精度需求

YAML
# ❌ 错误:金额用 DOUBLE,会有浮点精度问题
amount:
  type: DOUBLE

# ✅ 正确:金额用 DECIMAL
amount:
  type: DECIMAL
  constraints:
    precision: 10
    scale: 2

#错误 3:嵌套过深

YAML
# ❌ 错误:3 层嵌套,查询困难
address:
  type: STRUCT
  structType: Address    # Address 内又嵌套 City,City 内又嵌套 Country

# ✅ 正确:扁平化 + 引用
city:
  type: REFERENCE
  targetType: City
address:
  type: STRUCT
  structType: SimpleAddress   # 只包含 street, zipCode

#错误 4:主键选择不当

YAML
# ❌ 错误:用邮箱做主键(可能变更)
primaryKey: email

# ❌ 错误:纯自增 ID(不支持分布式)
primaryKey:
  strategy: SEQUENCE

# ✅ 正确:语义化 ID
primaryKey:
  property: customerId
  strategy: SEMANTIC_ID
  prefix: "CUS"

#Key Takeaways

  1. 选择正确的属性类型是 Schema 设计的第一步。20+ 种类型覆盖了从简单标量到地理空间、向量嵌入的所有场景。优先使用最精确的类型(ENUM 而非 STRING,DECIMAL 而非 DOUBLE),让类型系统替你做验证。

  2. 约束是数据质量的第一道防线。三层约束体系(字段级→对象级→关系级)将业务规则下沉到 Schema 层,避免"应用层校验不一致"的经典问题。

  3. 主键设计影响深远。语义化 ID 方案兼顾了可读性、分布式友好和类型识别,是 coomia-dip 的推荐策略。避免用可变业务字段做主键。

#下一篇

S4-03: ObjectType 生命周期:从 DRAFT 到 ARCHIVED 的状态机 —— 我们将深入 ObjectType 的 4 个生命周期状态、转换规则和兼容性检查。

tags: object-type, property-types, constraints, primary-key, schema-design, coomia-dip, data-modeling, validation