ObjectType 生命周期:从 DRAFT 到 ARCHIVED 的状态机
在传统开发中,数据库 Schema 变更是一个危险的操作:
Coomia发布于 2025年8月6日17 分钟阅读
分享本文Twitter / X
ObjectType 生命周期:从 DRAFT 到 ARCHIVED 的状态机
“系列:S4 本体建模 · 第 3 篇 | 难度:中级 | 阅读时间:18 分钟
#TL;DR
- **Schema 生命周期四阶段(DRAFT → ACTIVE → DEPRECATED → ARCHIVED)**确保了 Ontology 变更的安全性和可追溯性,每次状态转换都有严格的前置条件检查。
- 兼容性验证引擎在每次变更时自动检测破坏性更改(删除必填属性、修改主键类型等),防止"改了 Schema 却没改消费者"的灾难性场景。
- 安全删除协议通过依赖图分析确保被删除的类型不会留下悬挂引用,支持 dry-run 预检和强制删除两种模式。
#1. 为什么 Schema 需要生命周期管理
在传统开发中,数据库 Schema 变更是一个危险的操作:
Code
传统方式(危险):
┌─────────────┐
│ ALTER TABLE │ → 立即生效
│ DROP COLUMN │ → 无法回退
│ RENAME │ → 消费者全挂
└─────────────┘
开发者:写个 Flyway 脚本 → DBA 审核(也许)→ 直接执行 → 祈祷不出事
coomia-dip 方式(安全):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DRAFT │───►│ ACTIVE │───►│ DEPRECATED │───►│ ARCHIVED │
│ (草稿) │ │ (生产可用) │ │ (计划下线) │ │ (已归档) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
可自由修改 兼容性变更only 通知消费者迁移 只读,不可修改
#2. 四个生命周期状态详解
#2.1 DRAFT — 草稿状态
Code
┌──────────────────────────────────────┐
│ DRAFT │
│ │
│ 特性: │
│ ├── 可以自由添加/删除/修改属性 │
│ ├── 可以修改主键定义 │
│ ├── 可以修改关系定义 │
│ ├── 不会生成 API │
│ ├── 不会创建物理存储 │
│ └── 可以直接删除(无需依赖检查) │
│ │
│ 可用操作: │
│ ├── addProperty │
│ ├── removeProperty │
│ ├── modifyProperty │
│ ├── setPrimaryKey │
│ ├── addRelation │
│ ├── publish → 转为 ACTIVE │
│ └── delete → 永久删除 │
└──────────────────────────────────────┘
Python
from ontology_sdk import OntologyClient
client = OntologyClient(base_url="http://localhost:8080")
# 创建 DRAFT 状态的 ObjectType
result = client.schema.create_object_type({
"name": "Equipment",
"primaryKey": "equipmentId",
"properties": {
"equipmentId": {"type": "STRING", "required": True},
"name": {"type": "STRING", "required": True},
}
})
assert result.lifecycle == "DRAFT"
# DRAFT 状态下可以自由修改
client.schema.add_property("Equipment", {
"status": {"type": "ENUM", "enumValues": ["RUNNING", "STOPPED"]}
})
client.schema.remove_property("Equipment", "name") # 可以删除
client.schema.add_property("Equipment", {
"displayName": {"type": "STRING", "required": True} # 重新添加
})
#2.2 ACTIVE — 激活状态
Code
┌──────────────────────────────────────┐
│ ACTIVE │
│ │
│ 特性: │
│ ├── 平台自动生成 CRUD API │
│ ├── 自动创建物理存储(Doris 表等) │
│ ├── 可以被其他类型引用 │
│ ├── 只允许兼容性变更 │
│ ├── 可以创建/查询实例数据 │
│ └── 版本号自动递增 │
│ │
│ 允许的变更(兼容): │
│ ├── 添加可选属性 │
│ ├── 增加 ENUM 枚举值 │
│ ├── 放宽约束(增大 maxLength) │
│ ├── 添加索引 │
│ ├── 修改 displayName / description │
│ └── 添加新的 InterfaceType 实现 │
│ │
│ 禁止的变更(破坏性): │
│ ├── 删除属性 │
│ ├── 修改属性类型 │
│ ├── 修改主键 │
│ ├── 添加必填属性(无默认值) │
│ ├── 删除 ENUM 枚举值 │
│ ├── 收紧约束(缩小 maxLength) │
│ └── 修改关系的基数 │
│ │
│ 可用操作: │
│ ├── compatibleChange (兼容变更) │
│ ├── deprecate → 转为 DEPRECATED │
│ └── 破坏性变更需要通过 Proposal 流程 │
└──────────────────────────────────────┘
Python
# 发布到 ACTIVE
client.schema.publish_object_type("Equipment")
# 兼容性变更 — 成功
client.schema.add_property("Equipment", {
"description": {"type": "STRING"} # 可选属性,OK
})
# 兼容性变更 — 增加枚举值,OK
client.schema.add_enum_value("Equipment", "status", "MAINTENANCE")
# 破坏性变更 — 会被拒绝!
try:
client.schema.remove_property("Equipment", "displayName")
except IncompatibleChangeError as e:
print(f"拒绝: {e}")
# 拒绝: Cannot remove required property 'displayName' from ACTIVE ObjectType.
# Use a Proposal to schedule this breaking change.
#2.3 DEPRECATED — 弃用状态
Code
┌──────────────────────────────────────┐
│ DEPRECATED │
│ │
│ 特性: │
│ ├── API 仍然可用(但返回警告头) │
│ ├── 实例数据仍然可读写 │
│ ├── 新代码不应该再引用此类型 │
│ ├── 平台生成弃用通知 │
│ ├── 可以设置 sunsetDate(日落日期) │
│ └── 到期后自动转为 ARCHIVED │
│ │
│ 进入条件: │
│ ├── 必须从 ACTIVE 状态转入 │
│ ├── 必须指定替代方案或迁移指引 │
│ └── 必须通知所有已知消费者 │
│ │
│ 可用操作: │
│ ├── reactivate → 回到 ACTIVE │
│ ├── archive → 转为 ARCHIVED │
│ └── 有限的兼容性变更(仅 bug fix) │
└──────────────────────────────────────┘
Python
# 弃用
client.schema.deprecate_object_type(
name="Equipment",
reason="Replaced by EquipmentV2 with better property design",
replacement="EquipmentV2",
sunset_date="2026-06-01",
migration_guide="See docs/migration/equipment-v2.md"
)
# API 响应会包含弃用警告
# HTTP/1.1 200 OK
# Deprecation: true
# Sunset: Sat, 01 Jun 2026 00:00:00 GMT
# Link: <docs/migration/equipment-v2.md>; rel="successor-version"
# 可以反悔——回到 ACTIVE
client.schema.reactivate_object_type("Equipment")
#2.4 ARCHIVED — 归档状态
Code
┌──────────────────────────────────────┐
│ ARCHIVED │
│ │
│ 特性: │
│ ├── API 不再可用(返回 410 Gone) │
│ ├── 实例数据已迁移或删除 │
│ ├── Schema 定义保留(审计用) │
│ ├── 不可修改、不可回退 │
│ └── 保留在 SchemaRegistry 历史中 │
│ │
│ 进入条件: │
│ ├── 必须从 DEPRECATED 状态转入 │
│ ├── 所有实例数据必须已清零或迁移 │
│ ├── 所有依赖此类型的 RelationType │
│ │ 必须已删除或重定向 │
│ └── 所有依赖此类型的 ActionType │
│ 必须已删除或重定向 │
│ │
│ 不可执行任何修改操作 │
└──────────────────────────────────────┘
Python
# 归档(需要满足所有前置条件)
try:
client.schema.archive_object_type("Equipment")
except ArchivePreConditionError as e:
print(f"前置条件不满足: {e}")
# 前置条件不满足:
# - 3 active relations reference 'Equipment'
# - 127 instances still exist
# - 2 ActionTypes target 'Equipment'
# 先清理依赖
client.schema.delete_relation_type("EquipmentBelongsToLine")
client.data.migrate_instances("Equipment", "EquipmentV2", mapping={...})
client.schema.delete_action_type("ScheduleMaintenance")
# 再次归档
client.schema.archive_object_type("Equipment") # 成功
#3. 状态转换完整图
Code
┌──────────┐
│ CREATE │
└────┬─────┘
│
▼
┌──────────────────┐
│ DRAFT │◄──────────────────────┐
│ │ │
│ 自由修改 │ clone() │
│ 无 API │ (从任何状态克隆) │
│ 无存储 │ │
└────┬────────┬───┘ │
│ │ │
publish delete │
│ │ │
▼ ▼ │
┌─────────────┐ (永久删除) │
│ ACTIVE │ │
│ │ │
│ API 可用 │ ◄─── reactivate ────┐ │
│ 存储已创建 │ │ │
│ 兼容变更 only │ │ │
└────┬─────────┘ │ │
│ │ │
deprecate │ │
│ │ │
▼ │ │
┌──────────────────┐ │ │
│ DEPRECATED │──────────────────────┘ │
│ │ │
│ API 带警告 │ │
│ 通知消费者 │ ──── clone("EquipmentV3") ───┘
│ 有日落日期 │
└────┬──────────────┘
│
archive
│
▼
┌──────────────────┐
│ ARCHIVED │
│ │
│ API 返回 410 │
│ 只读历史 │
│ 不可回退 │
└──────────────────┘
#4. 兼容性验证引擎
#4.1 变更类型分类
Code
┌─────────────────────────────────────────────────┐
│ 变更类型分类矩阵 │
├─────────────────────┬───────────┬───────────────┤
│ 变更操作 │ 兼容性 │ ACTIVE 下允许 │
├─────────────────────┼───────────┼───────────────┤
│ 添加可选属性 │ 兼容 ✅ │ 是 │
│ 添加有默认值的必填属性│ 兼容 ✅ │ 是 │
│ 添加 ENUM 枚举值 │ 兼容 ✅ │ 是 │
│ 放宽 maxLength │ 兼容 ✅ │ 是 │
│ 添加索引 │ 兼容 ✅ │ 是 │
│ 修改 displayName │ 兼容 ✅ │ 是 │
│ 添加接口实现 │ 兼容 ✅ │ 是 │
├─────────────────────┼───────────┼───────────────┤
│ 删除可选属性 │ 破坏 ❌ │ 需 Proposal │
│ 删除必填属性 │ 破坏 ❌ │ 需 Proposal │
│ 修改属性类型 │ 破坏 ❌ │ 需 Proposal │
│ 修改主键 │ 破坏 ❌ │ 需 Proposal │
│ 添加无默认值的必填属性│ 破坏 ❌ │ 需 Proposal │
│ 删除 ENUM 枚举值 │ 破坏 ❌ │ 需 Proposal │
│ 收紧约束 │ 破坏 ❌ │ 需 Proposal │
│ 修改关系基数 │ 破坏 ❌ │ 需 Proposal │
│ 删除接口实现 │ 破坏 ❌ │ 需 Proposal │
└─────────────────────┴───────────┴───────────────┘
#4.2 兼容性检查流程
Code
┌───────────────┐
│ 变更请求 │
└───────┬───────┘
│
┌───────▼───────┐
│ 当前状态检查 │
└───────┬───────┘
│
┌─────────────┼─────────────┐
│ │ │
DRAFT ACTIVE DEPRECATED
│ │ │
直接应用 ┌──────▼──────┐ 仅 bug fix
│ 兼容性分析 │
└──────┬──────┘
│
┌─────────┼─────────┐
│ │
兼容变更 破坏性变更
│ │
直接应用 ┌───────▼───────┐
│ Proposal 流程 │
│ │
│ 1. 创建 Proposal│
│ 2. 影响分析 │
│ 3. 审核 │
│ 4. 执行 │
└────────────────┘
#4.3 影响分析报告
Python
# 分析破坏性变更的影响
impact = client.schema.analyze_impact(
object_type="Equipment",
change={
"type": "REMOVE_PROPERTY",
"property": "status"
}
)
print(impact.report())
# Impact Analysis Report
# ═══════════════════════════════════════════
# Change: Remove property 'status' from Equipment
# Severity: BREAKING
#
# Affected Components:
# ┌──────────────────────────────────────────┐
# │ Component │ Count │ Severity │
# ├────────────────────┼───────┼─────────────┤
# │ RelationTypes │ 0 │ - │
# │ ActionTypes │ 2 │ HIGH │
# │ - ScheduleMaint │ │ Uses status │
# │ - TransferEquip │ │ Uses status │
# │ Derived Props │ 1 │ HIGH │
# │ - isOperational │ │ Depends on │
# │ Metrics │ 1 │ MEDIUM │
# │ - StatusDistrib │ │ Groups by │
# │ API Consumers │ 5 │ HIGH │
# │ Dashboard Widgets │ 3 │ MEDIUM │
# └──────────────────────────────────────────┘
#
# Recommendation: Create a Proposal with migration plan
#5. 版本管理
#5.1 语义化版本号
Code
版本号格式:MAJOR.MINOR.PATCH
MAJOR:破坏性变更(需要 Proposal)
MINOR:兼容性新增(新属性、新枚举值)
PATCH:元数据修改(displayName、description)
Equipment v1.0.0 → v1.1.0(添加可选属性 description)
Equipment v1.1.0 → v1.2.0(添加 ENUM 值 MAINTENANCE)
Equipment v1.2.0 → v2.0.0(通过 Proposal 删除属性 oldField)
#5.2 版本历史查询
Python
# 查看版本历史
history = client.schema.get_version_history("Equipment")
for version in history:
print(f"v{version.number} | {version.timestamp} | "
f"{version.change_type} | {version.author}")
# v1.0.0 | 2026-01-15 | CREATED | admin
# v1.1.0 | 2026-01-20 | ADD_PROP | dev-team
# v1.2.0 | 2026-02-01 | ADD_ENUM | dev-team
# v2.0.0 | 2026-03-01 | BREAKING | architect (Proposal #42)
# 查看特定版本的 Schema
schema_v1 = client.schema.get_object_type("Equipment", version="1.0.0")
#5.3 版本对比
Python
diff = client.schema.diff_versions("Equipment", "1.0.0", "2.0.0")
print(diff.summary())
# Schema Diff: Equipment v1.0.0 → v2.0.0
# ════════════════════════════════════════
# Added Properties:
# + description: STRING (optional)
# + maintenanceDate: TIMESTAMP (optional)
#
# Modified Properties:
# ~ status: ENUM added value 'MAINTENANCE'
#
# Removed Properties:
# - oldField: STRING (was optional)
#
# Compatibility: BREAKING (removed property)
#6. 安全删除协议
#6.1 依赖图分析
Code
被删除的 ObjectType: Equipment
依赖图:
┌──────────────┐
│ Equipment │ ← 要删除这个
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Relations │ │ Actions │ │ Derived │
│ │ │ │ │ Props │
│ BelongsTo │ │ Schedule │ │ OEE │
│ Line (3) │ │ Maint.(2) │ │ Metrics(1)│
└────────────┘ └───────────┘ └───────────┘
│
┌─────▼──────┐
│ ProductLine │
│ (反向引用) │
└────────────┘
安全删除需要先清理所有依赖节点
#6.2 Dry-Run 预检
Python
# dry-run 模式:只检查不执行
result = client.schema.delete_object_type(
"Equipment",
mode="DRY_RUN"
)
if result.can_delete:
print("可以安全删除")
else:
print(f"无法删除,原因:")
for blocker in result.blockers:
print(f" - {blocker.type}: {blocker.name} ({blocker.reason})")
# 无法删除,原因:
# - RELATION: EquipmentBelongsToLine (references Equipment as source)
# - RELATION: LineContainsEquipment (references Equipment as target)
# - ACTION: ScheduleMaintenance (targets Equipment)
# - ACTION: TransferEquipment (targets Equipment)
# - DERIVED_PROP: Equipment.oee (depends on Equipment properties)
# - METRIC: EquipmentOEE (aggregates Equipment.oee)
# - INSTANCES: 1,247 instances exist
#6.3 级联删除
Python
# 强制删除(级联清理所有依赖)
result = client.schema.delete_object_type(
"Equipment",
mode="CASCADE",
confirm=True, # 需要显式确认
backup=True # 删前备份 Schema 定义
)
# 执行顺序:
# 1. 备份 Schema 定义 → schema-backups/Equipment-v2.0.0.json
# 2. 删除依赖的 Metrics
# 3. 删除依赖的 ActionTypes
# 4. 删除依赖的 Derived Properties
# 5. 删除依赖的 RelationTypes
# 6. 迁移/删除实例数据
# 7. 删除物理存储(Doris 表)
# 8. 从 SchemaRegistry 移除
#7. 多环境生命周期管理
Code
┌─────────────────────────────────────────────────────────┐
│ Environment Promotion │
│ │
│ DEV STAGING PRODUCTION │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ DRAFT │ │ │ │ │ │
│ │ ↓ │ │ │ │ │ │
│ │ ACTIVE │──promote──│ ACTIVE │──promote──│ ACTIVE │ │
│ │ │ │ ↓ │ │ │ │
│ │ │ │ test │ │ │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ 每个环境独立的生命周期状态 │
│ promote 操作会复制 Schema + 运行兼容性检查 │
└─────────────────────────────────────────────────────────┘
Python
# 从 DEV 提升到 STAGING
client.schema.promote(
object_type="Equipment",
from_env="dev",
to_env="staging",
version="2.0.0"
)
# 从 STAGING 提升到 PRODUCTION
client.schema.promote(
object_type="Equipment",
from_env="staging",
to_env="production",
version="2.0.0",
require_approval=True, # 需要审批
approvers=["architect-team"]
)
#8. 生命周期事件与钩子
Python
# 注册生命周期事件监听器
@client.schema.on_lifecycle_change("Equipment")
def on_equipment_lifecycle(event):
"""
event.object_type: "Equipment"
event.old_state: "ACTIVE"
event.new_state: "DEPRECATED"
event.reason: "Replaced by EquipmentV2"
event.actor: "architect@company.com"
event.timestamp: "2026-03-01T10:00:00Z"
"""
if event.new_state == "DEPRECATED":
# 通知所有消费团队
notify_consumers(event.object_type, event.reason)
# 创建 JIRA ticket 跟踪迁移
create_migration_ticket(event)
elif event.new_state == "ARCHIVED":
# 清理相关资源
cleanup_resources(event.object_type)
# 注册 pre-hook(可以阻止状态转换)
@client.schema.before_lifecycle_change("Equipment")
def before_equipment_change(event):
if event.new_state == "ARCHIVED":
# 检查是否还有活跃的 API 调用
if get_api_call_count(event.object_type, last_days=30) > 0:
raise BlockTransitionError(
"Cannot archive: still has API calls in the last 30 days"
)
#9. 生命周期管理最佳实践
#9.1 Schema 演进策略
Code
策略一:就地修改(In-Place Evolution)
适用于:兼容性变更
┌──────────────┐ ┌──────────────┐
│ Equipment │ │ Equipment │
│ v1.0.0 │ ──► │ v1.1.0 │
│ ACTIVE │ │ ACTIVE │
│ │ │ +description │
└──────────────┘ └──────────────┘
策略二:并行版本(Side-by-Side)
适用于:破坏性变更,需要迁移时间
┌──────────────┐ ┌──────────────┐
│ Equipment │ │ EquipmentV2 │
│ v2.0.0 │ │ v1.0.0 │
│ DEPRECATED │ ──► │ ACTIVE │
│ sunset: 6/1 │ │ (新设计) │
└──────────────┘ └──────────────┘
两个版本并行运行,直到旧版本日落
策略三:克隆演进(Clone and Evolve)
适用于:大规模重构
┌──────────────┐ clone ┌──────────────┐
│ Equipment │ ──────────► │ EquipmentV3 │
│ ACTIVE │ │ DRAFT │
└──────────────┘ └──────┬───────┘
│ 修改
▼
┌──────────────┐
│ EquipmentV3 │
│ ACTIVE │
└──────────────┘
#9.2 检查清单
Code
□ DRAFT → ACTIVE 前检查:
├── □ 有且仅有一个主键属性
├── □ 至少有一个非主键属性
├── □ 所有必填属性有合理的默认值或业务来源
├── □ 所有 REFERENCE 属性指向已存在的 ObjectType
├── □ 所有 StructType 引用已注册
├── □ 所有 InterfaceType 的必需属性已实现
└── □ 命名符合 camelCase 规范
□ ACTIVE → DEPRECATED 前检查:
├── □ 指定了替代方案或迁移指引
├── □ 设置了合理的日落日期(至少 30 天后)
├── □ 通知了所有已知消费团队
└── □ 创建了迁移计划
□ DEPRECATED → ARCHIVED 前检查:
├── □ 所有实例数据已迁移或删除
├── □ 所有依赖关系已清理
├── □ 所有 ActionType 已重定向或删除
├── □ 最近 30 天无 API 调用
└── □ 已备份 Schema 定义
#10. 常见问题解答
#Q1:ACTIVE 状态下必须做破坏性变更怎么办?
使用 Proposal 流程(详见 S4-09):
Python
proposal = client.schema.create_proposal(
title="Remove deprecated field 'oldStatus' from Equipment",
changes=[
{"type": "REMOVE_PROPERTY", "objectType": "Equipment", "property": "oldStatus"}
],
migration_plan="Step 1: Update all consumers to use 'status' instead...",
rollback_plan="Re-add 'oldStatus' as optional with data backfill"
)
# 提交审核
proposal.submit_for_review(reviewers=["architect-team"])
# 审核通过后执行
proposal.execute() # 自动执行变更 + 版本号升级为 MAJOR
#Q2:Schema 修改后如何通知消费者?
Python
# 查询所有消费者
consumers = client.schema.get_consumers("Equipment")
for consumer in consumers:
print(f"{consumer.type}: {consumer.name} (last access: {consumer.last_access})")
# SDK_CLIENT: frontend-app (last access: 2 min ago)
# SDK_CLIENT: data-pipeline (last access: 1 hour ago)
# DASHBOARD: equipment-monitor (last access: 5 min ago)
# ACTION: ScheduleMaintenance (always active)
# 自动通知
client.schema.notify_consumers(
"Equipment",
message="Property 'oldStatus' will be removed on 2026-06-01. "
"Please migrate to 'status'.",
channel=["email", "webhook"]
)
#Q3:误操作把 ACTIVE 改成 DEPRECATED 怎么办?
Python
# 在日落日期前可以回退
client.schema.reactivate_object_type("Equipment")
# 状态回到 ACTIVE,API 恢复正常,弃用警告移除
#Key Takeaways
-
四阶段生命周期是 Schema 安全演进的保障。DRAFT 允许自由实验,ACTIVE 限制为兼容变更,DEPRECATED 给消费者迁移时间,ARCHIVED 保留历史审计记录。这比"ALTER TABLE + 祈祷"安全 100 倍。
-
兼容性验证引擎是自动的守门员。每次对 ACTIVE 类型的变更都经过兼容性检查,破坏性变更必须走 Proposal 流程——这消除了"开发改了 Schema 但忘了通知前端"的经典灾难。
-
安全删除协议防止悬挂引用。通过 dry-run 预检和依赖图分析,确保删除操作不会留下断裂的关系链。即使使用级联删除,也会先备份 Schema 定义。
#下一篇
S4-04: RelationType 与知识图谱:如何用关系连接业务世界 —— 我们将深入 RelationType 的关系建模、多跳遍历和图布局。
tags: object-type, lifecycle, state-machine, schema-evolution, compatibility, versioning, coomia-dip, deprecation