ObjectType Lifecycle: The State Machine from DRAFT to ARCHIVED
In traditional development, database Schema changes are dangerous operations:
ObjectType Lifecycle: The State Machine from DRAFT to ARCHIVED
“Series: S4 Ontology Modeling · Article 3 | Level: Intermediate | Reading Time: 18 min
#TL;DR
- The four-stage Schema lifecycle (DRAFT → ACTIVE → DEPRECATED → ARCHIVED) ensures the safety and traceability of Ontology changes, with strict precondition checks at every state transition.
- The compatibility validation engine automatically detects breaking changes (removing required properties, modifying primary key types, etc.) at every change, preventing the catastrophic scenario of "changed the Schema but forgot the consumers."
- The safe deletion protocol uses dependency graph analysis to ensure deleted types leave no dangling references, supporting both dry-run preview and force-delete modes.
#1. Why Schema Needs Lifecycle Management
In traditional development, database Schema changes are dangerous operations:
Traditional Approach (Dangerous):
┌─────────────┐
│ ALTER TABLE │ → Takes effect immediately
│ DROP COLUMN │ → Cannot roll back
│ RENAME │ → All consumers break
└─────────────┘
Developer: Write a Flyway script → DBA reviews (maybe) → Execute → Pray
coomia-dip Approach (Safe):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DRAFT │───►│ ACTIVE │───►│ DEPRECATED │───►│ ARCHIVED │
│ (draft) │ │ (production)│ │(winding down)│ │ (archived) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Free to modify Compatible only Notify consumers Read-only
#2. Four Lifecycle States in Detail
#2.1 DRAFT — Draft State
┌──────────────────────────────────────┐
│ DRAFT │
│ │
│ Characteristics: │
│ ├── Free to add/remove/modify props │
│ ├── Can modify primary key │
│ ├── Can modify relation definitions │
│ ├── No API generated │
│ ├── No physical storage created │
│ └── Can delete directly (no checks) │
│ │
│ Available Operations: │
│ ├── addProperty │
│ ├── removeProperty │
│ ├── modifyProperty │
│ ├── setPrimaryKey │
│ ├── addRelation │
│ ├── publish → transition to ACTIVE │
│ └── delete → permanent deletion │
└──────────────────────────────────────┘
from ontology_sdk import OntologyClient
client = OntologyClient(base_url="http://localhost:8080")
# Create ObjectType in DRAFT state
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"
# Free to modify in DRAFT state
client.schema.add_property("Equipment", {
"status": {"type": "ENUM", "enumValues": ["RUNNING", "STOPPED"]}
})
client.schema.remove_property("Equipment", "name") # Can delete
client.schema.add_property("Equipment", {
"displayName": {"type": "STRING", "required": True} # Re-add
})
#2.2 ACTIVE — Active State
┌──────────────────────────────────────┐
│ ACTIVE │
│ │
│ Characteristics: │
│ ├── Platform auto-generates CRUD API│
│ ├── Physical storage auto-created │
│ ├── Can be referenced by other types│
│ ├── Only compatible changes allowed │
│ ├── Can create/query instance data │
│ └── Version number auto-increments │
│ │
│ Allowed Changes (Compatible): │
│ ├── Add optional properties │
│ ├── Add ENUM values │
│ ├── Relax constraints (widen max) │
│ ├── Add indexes │
│ ├── Modify displayName/description │
│ └── Add InterfaceType implementations│
│ │
│ Forbidden Changes (Breaking): │
│ ├── Remove properties │
│ ├── Change property types │
│ ├── Modify primary key │
│ ├── Add required prop (no default) │
│ ├── Remove ENUM values │
│ ├── Tighten constraints │
│ └── Change relation cardinality │
│ │
│ Available Operations: │
│ ├── compatibleChange │
│ ├── deprecate → DEPRECATED │
│ └── Breaking changes require │
│ Proposal workflow │
└──────────────────────────────────────┘
# Publish to ACTIVE
client.schema.publish_object_type("Equipment")
# Compatible change — succeeds
client.schema.add_property("Equipment", {
"description": {"type": "STRING"} # Optional property, OK
})
# Compatible change — add enum value, OK
client.schema.add_enum_value("Equipment", "status", "MAINTENANCE")
# Breaking change — will be rejected!
try:
client.schema.remove_property("Equipment", "displayName")
except IncompatibleChangeError as e:
print(f"Rejected: {e}")
# Rejected: Cannot remove required property 'displayName' from ACTIVE ObjectType.
# Use a Proposal to schedule this breaking change.
#2.3 DEPRECATED — Deprecated State
┌──────────────────────────────────────┐
│ DEPRECATED │
│ │
│ Characteristics: │
│ ├── API still works (with warnings) │
│ ├── Instance data still read/write │
│ ├── New code should not reference │
│ ├── Platform generates deprecation │
│ │ notifications │
│ ├── Can set sunsetDate │
│ └── Auto-transitions to ARCHIVED │
│ after sunset │
│ │
│ Entry Conditions: │
│ ├── Must transition from ACTIVE │
│ ├── Must specify alternative or │
│ │ migration guide │
│ └── Must notify all known consumers │
│ │
│ Available Operations: │
│ ├── reactivate → back to ACTIVE │
│ ├── archive → to ARCHIVED │
│ └── Limited compatible changes only │
└──────────────────────────────────────┘
# Deprecate
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 responses include deprecation warnings
# 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"
# Can revert — back to ACTIVE
client.schema.reactivate_object_type("Equipment")
#2.4 ARCHIVED — Archived State
┌──────────────────────────────────────┐
│ ARCHIVED │
│ │
│ Characteristics: │
│ ├── API no longer available (410) │
│ ├── Instance data migrated/deleted │
│ ├── Schema definition retained │
│ │ (for audit purposes) │
│ ├── Cannot modify or revert │
│ └── Preserved in SchemaRegistry │
│ history │
│ │
│ Entry Conditions: │
│ ├── Must transition from DEPRECATED │
│ ├── All instance data must be zero │
│ │ or migrated │
│ ├── All RelationTypes referencing │
│ │ this type must be deleted │
│ └── All ActionTypes targeting │
│ this type must be deleted │
│ │
│ No modification operations allowed │
└──────────────────────────────────────┘
# Archive (must satisfy all preconditions)
try:
client.schema.archive_object_type("Equipment")
except ArchivePreConditionError as e:
print(f"Preconditions not met: {e}")
# Preconditions not met:
# - 3 active relations reference 'Equipment'
# - 127 instances still exist
# - 2 ActionTypes target 'Equipment'
# Clean up dependencies first
client.schema.delete_relation_type("EquipmentBelongsToLine")
client.data.migrate_instances("Equipment", "EquipmentV2", mapping={...})
client.schema.delete_action_type("ScheduleMaintenance")
# Archive again
client.schema.archive_object_type("Equipment") # Success
#3. Complete State Transition Diagram
┌──────────┐
│ CREATE │
└────┬─────┘
│
▼
┌──────────────────┐
│ DRAFT │◄──────────────────────┐
│ │ │
│ Free to modify │ clone() │
│ No API │ (clone from any │
│ No storage │ state) │
└────┬────────┬───┘ │
│ │ │
publish delete │
│ │ │
▼ ▼ │
┌─────────────┐ (permanent) │
│ ACTIVE │ │
│ │ │
│ API enabled │ ◄─── reactivate ────┐ │
│ Storage on │ │ │
│ Compat. only │ │ │
└────┬─────────┘ │ │
│ │ │
deprecate │ │
│ │ │
▼ │ │
┌──────────────────┐ │ │
│ DEPRECATED │──────────────────────┘ │
│ │ │
│ API with warnings│ │
│ Notify consumers │ ──── clone("EquipmentV3") ───┘
│ Sunset date set │
└────┬──────────────┘
│
archive
│
▼
┌──────────────────┐
│ ARCHIVED │
│ │
│ API returns 410 │
│ Read-only history│
│ Cannot revert │
└──────────────────┘
#4. Compatibility Validation Engine
#4.1 Change Type Classification
┌─────────────────────────────────────────────────┐
│ Change Type Classification Matrix │
├─────────────────────┬───────────┬───────────────┤
│ Change Operation │ Compat. │ Allowed ACTIVE│
├─────────────────────┼───────────┼───────────────┤
│ Add optional property│ Compat ✅ │ Yes │
│ Add req'd w/ default │ Compat ✅ │ Yes │
│ Add ENUM value │ Compat ✅ │ Yes │
│ Relax maxLength │ Compat ✅ │ Yes │
│ Add index │ Compat ✅ │ Yes │
│ Modify displayName │ Compat ✅ │ Yes │
│ Add interface impl │ Compat ✅ │ Yes │
├─────────────────────┼───────────┼───────────────┤
│ Remove optional prop │ Breaking ❌│ Need Proposal │
│ Remove required prop │ Breaking ❌│ Need Proposal │
│ Change property type │ Breaking ❌│ Need Proposal │
│ Modify primary key │ Breaking ❌│ Need Proposal │
│ Add req'd no default │ Breaking ❌│ Need Proposal │
│ Remove ENUM value │ Breaking ❌│ Need Proposal │
│ Tighten constraints │ Breaking ❌│ Need Proposal │
│ Change cardinality │ Breaking ❌│ Need Proposal │
│ Remove interface impl│ Breaking ❌│ Need Proposal │
└─────────────────────┴───────────┴───────────────┘
#4.2 Compatibility Check Flow
┌───────────────┐
│ Change Request │
└───────┬───────┘
│
┌───────▼───────┐
│ State Check │
└───────┬───────┘
│
┌─────────────┼─────────────┐
│ │ │
DRAFT ACTIVE DEPRECATED
│ │ │
Apply ┌────▼────┐ Bug fix only
directly │Compat. │
│Analysis │
└────┬────┘
│
┌─────────┼─────────┐
│ │
Compatible Breaking
│ │
Apply ┌────▼─────────┐
directly │ Proposal │
│ Workflow │
│ 1. Create │
│ 2. Impact │
│ 3. Review │
│ 4. Execute │
└───────────────┘
#4.3 Impact Analysis Report
# Analyze the impact of a breaking change
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. Version Management
#5.1 Semantic Versioning
Version format: MAJOR.MINOR.PATCH
MAJOR: Breaking changes (require Proposal)
MINOR: Compatible additions (new properties, new enum values)
PATCH: Metadata modifications (displayName, description)
Equipment v1.0.0 → v1.1.0 (add optional property description)
Equipment v1.1.0 → v1.2.0 (add ENUM value MAINTENANCE)
Equipment v1.2.0 → v2.0.0 (remove property oldField via Proposal)
#5.2 Version History Query
# View version history
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)
# View Schema at a specific version
schema_v1 = client.schema.get_object_type("Equipment", version="1.0.0")
#5.3 Version Comparison
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. Safe Deletion Protocol
#6.1 Dependency Graph Analysis
ObjectType to delete: Equipment
Dependency Graph:
┌──────────────┐
│ Equipment │ ← Want to delete this
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Relations │ │ Actions │ │ Derived │
│ │ │ │ │ Props │
│ BelongsTo │ │ Schedule │ │ OEE │
│ Line (3) │ │ Maint.(2) │ │ Metrics(1)│
└────────────┘ └───────────┘ └───────────┘
│
┌─────▼──────┐
│ ProductLine │
│ (back-ref) │
└────────────┘
Safe deletion requires cleaning all dependency nodes first
#6.2 Dry-Run Preview
# Dry-run mode: check only, don't execute
result = client.schema.delete_object_type(
"Equipment",
mode="DRY_RUN"
)
if result.can_delete:
print("Safe to delete")
else:
print(f"Cannot delete. Reasons:")
for blocker in result.blockers:
print(f" - {blocker.type}: {blocker.name} ({blocker.reason})")
# Cannot delete. Reasons:
# - 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 Cascade Delete
# Force delete (cascade-clean all dependencies)
result = client.schema.delete_object_type(
"Equipment",
mode="CASCADE",
confirm=True, # Explicit confirmation required
backup=True # Backup Schema definition before deletion
)
# Execution order:
# 1. Backup Schema definition → schema-backups/Equipment-v2.0.0.json
# 2. Delete dependent Metrics
# 3. Delete dependent ActionTypes
# 4. Delete dependent Derived Properties
# 5. Delete dependent RelationTypes
# 6. Migrate/delete instance data
# 7. Delete physical storage (Doris table)
# 8. Remove from SchemaRegistry
#7. Multi-Environment Lifecycle Management
┌─────────────────────────────────────────────────────────┐
│ Environment Promotion │
│ │
│ DEV STAGING PRODUCTION │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ DRAFT │ │ │ │ │ │
│ │ ↓ │ │ │ │ │ │
│ │ ACTIVE │──promote──│ ACTIVE │──promote──│ ACTIVE │ │
│ │ │ │ ↓ │ │ │ │
│ │ │ │ test │ │ │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ Each environment has independent lifecycle states │
│ Promote operation copies Schema + runs compat checks │
└─────────────────────────────────────────────────────────┘
# Promote from DEV to STAGING
client.schema.promote(
object_type="Equipment",
from_env="dev",
to_env="staging",
version="2.0.0"
)
# Promote from STAGING to PRODUCTION
client.schema.promote(
object_type="Equipment",
from_env="staging",
to_env="production",
version="2.0.0",
require_approval=True, # Requires approval
approvers=["architect-team"]
)
#8. Lifecycle Events and Hooks
# Register lifecycle event listener
@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)
create_migration_ticket(event)
elif event.new_state == "ARCHIVED":
cleanup_resources(event.object_type)
# Register pre-hook (can block state transitions)
@client.schema.before_lifecycle_change("Equipment")
def before_equipment_change(event):
if event.new_state == "ARCHIVED":
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. Lifecycle Management Best Practices
#9.1 Schema Evolution Strategies
Strategy 1: In-Place Evolution
Use for: Compatible changes
┌──────────────┐ ┌──────────────┐
│ Equipment │ │ Equipment │
│ v1.0.0 │ ──► │ v1.1.0 │
│ ACTIVE │ │ ACTIVE │
│ │ │ +description │
└──────────────┘ └──────────────┘
Strategy 2: Side-by-Side
Use for: Breaking changes needing migration time
┌──────────────┐ ┌──────────────┐
│ Equipment │ │ EquipmentV2 │
│ v2.0.0 │ │ v1.0.0 │
│ DEPRECATED │ ──► │ ACTIVE │
│ sunset: 6/1 │ │ (new design) │
└──────────────┘ └──────────────┘
Both versions run in parallel until old version sunsets
Strategy 3: Clone and Evolve
Use for: Large-scale refactoring
┌──────────────┐ clone ┌──────────────┐
│ Equipment │ ──────────► │ EquipmentV3 │
│ ACTIVE │ │ DRAFT │
└──────────────┘ └──────┬───────┘
│ modify
▼
┌──────────────┐
│ EquipmentV3 │
│ ACTIVE │
└──────────────┘
#9.2 Checklists
□ Before DRAFT → ACTIVE:
├── □ Exactly one primary key property
├── □ At least one non-primary-key property
├── □ All required properties have sensible defaults
├── □ All REFERENCE properties point to existing ObjectTypes
├── □ All StructType references are registered
├── □ All InterfaceType required properties are implemented
└── □ Naming follows camelCase convention
□ Before ACTIVE → DEPRECATED:
├── □ Alternative or migration guide specified
├── □ Sunset date set (at least 30 days out)
├── □ All known consumer teams notified
└── □ Migration plan created
□ Before DEPRECATED → ARCHIVED:
├── □ All instance data migrated or deleted
├── □ All dependencies cleaned up
├── □ All ActionTypes redirected or deleted
├── □ No API calls in the last 30 days
└── □ Schema definition backed up
#10. Frequently Asked Questions
#Q1: What if I must make a breaking change to an ACTIVE type?
Use the Proposal workflow (see S4-09 for details):
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'...",
rollback_plan="Re-add 'oldStatus' as optional with data backfill"
)
proposal.submit_for_review(reviewers=["architect-team"])
proposal.execute() # Auto-executes change + bumps to MAJOR version
#Q2: How do I notify consumers after a Schema change?
consumers = client.schema.get_consumers("Equipment")
for consumer in consumers:
print(f"{consumer.type}: {consumer.name} "
f"(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)
client.schema.notify_consumers(
"Equipment",
message="Property 'oldStatus' will be removed on 2026-06-01.",
channel=["email", "webhook"]
)
#Q3: What if I accidentally deprecated an ACTIVE type?
# Can revert before the sunset date
client.schema.reactivate_object_type("Equipment")
# State returns to ACTIVE, API resumes normally, deprecation warnings removed
#Key Takeaways
-
The four-stage lifecycle is the safety net for Schema evolution. DRAFT allows free experimentation, ACTIVE restricts to compatible changes, DEPRECATED gives consumers time to migrate, ARCHIVED preserves audit history. This is 100x safer than "ALTER TABLE + pray."
-
The compatibility validation engine is an automatic gatekeeper. Every change to an ACTIVE type goes through compatibility checking; breaking changes must go through the Proposal workflow — eliminating the classic disaster of "developer changed the Schema but forgot to notify the frontend."
-
The safe deletion protocol prevents dangling references. Through dry-run preview and dependency graph analysis, deletion operations are guaranteed to leave no broken relation chains. Even cascade deletion backs up the Schema definition first.
#Next Article
S4-04: RelationType and Knowledge Graphs: Connecting the Business World with Relations — We'll dive into RelationType's relation modeling, multi-hop traversal, and graph layouts.
tags: object-type, lifecycle, state-machine, schema-evolution, compatibility, versioning, coomia-dip, deprecation