Back to Blog

ObjectType Deep Dive: Property Type System and Constraints

In traditional databases, you only have limited types like VARCHAR, INT, DECIMAL, TIMESTAMP. When business needs to express "this field can only be one of 3 values" or "this field is a nested address structure," you must handle it in the application layer.

CoomiaPublished on August 5, 202513 min read
Share this articleTwitter / X

ObjectType Deep Dive: Property Type System and Constraints

Series: S4 Ontology Modeling · Article 2 | Level: Intermediate | Reading Time: 18 min

#TL;DR

  • coomia-dip provides 20+ property types, from basic scalars (STRING / INTEGER / DOUBLE) to composite types (STRUCT / ARRAY / MAP / ENUM), covering all industrial-grade modeling needs.
  • The constraint system supports three levels: field-level (required / unique / range / pattern), object-level (cross-field rules), and relation-level (referential integrity) — ensuring data quality at the Schema layer, not the application layer.
  • Primary key design has 4 strategies (natural key / UUID / composite key / sequence key). Poor choices lead to data distribution skew and query performance degradation; this article provides a decision matrix.

#1. Why the Property Type System Matters

In traditional databases, you only have limited types like VARCHAR, INT, DECIMAL, TIMESTAMP. When business needs to express "this field can only be one of 3 values" or "this field is a nested address structure," you must handle it in the application layer.

coomia-dip's ObjectType shifts type checking to the Schema layer:

Code
Traditional Approach                  coomia-dip Approach
┌─────────────┐                      ┌──────────────────────┐
│  Database    │                      │   SchemaRegistry     │
│  VARCHAR(50) │ → Runtime check      │   STRING(maxLen=50)  │ → Definition-time
│  INT         │   App-layer valid.   │   ENUM(A,B,C)        │   Schema validation
│  TEXT(JSON)  │   No struct guaranty │   STRUCT(Address)    │   Struct guaranteed
└─────────────┘                      └──────────────────────┘

#2. Complete Property Type Catalog

#2.1 Scalar Types

TypeDescriptionDefault ConstraintsStorage Mapping
STRINGText stringmaxLength: 65535VARCHAR
INTEGER32-bit integermin: -2^31, max: 2^31-1INT
LONG64-bit integermin: -2^63, max: 2^63-1BIGINT
DOUBLE64-bit floating pointNoneDOUBLE
DECIMALExact decimalprecision: 38, scale: 10DECIMAL
BOOLEANBooleanNoneBOOLEAN
DATEDateformat: ISO-8601DATE
TIMESTAMPTimestamptimezone: UTCTIMESTAMP
TIMETimeformat: HH:mm:ssTIME
DURATIONTime durationformat: ISO-8601VARCHAR
YAML
# Scalar type examples
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"      # Special variable: current time

  isActive:
    type: BOOLEAN
    defaultValue: true

#2.2 Enum Type

YAML
properties:
  status:
    type: ENUM
    enumValues:
      - value: DRAFT
        displayName: "Draft"
        description: "Initial state"
      - value: ACTIVE
        displayName: "Active"
        description: "Currently in use"
      - value: DEPRECATED
        displayName: "Deprecated"
        description: "Planned for retirement"
      - value: ARCHIVED
        displayName: "Archived"
        description: "No longer available"
    defaultValue: DRAFT
    transitions:                    # Optional: state machine constraints
      DRAFT: [ACTIVE]
      ACTIVE: [DEPRECATED]
      DEPRECATED: [ARCHIVED]

ENUM type vs STRING + app-layer validation:

Code
STRING + App Layer:                  ENUM Type:
┌───────────────────┐              ┌───────────────────┐
│ DB stores "actve"  │ ← Typo     │ DB stores "ACTIVE" │ ← Schema validated
│ DB stores "Active" │ ← Case     │ Frontend gets list  │ ← Auto UI
│ Frontend hardcodes │             │ API auto-validates  │ ← No hand-writing
│ Each service dups  │             │ State machine opt.  │ ← Declarative
└───────────────────┘              └───────────────────┘

#2.3 Composite Types

YAML
properties:
  # STRUCT: Nested structure
  address:
    type: STRUCT
    structType: Address           # References a registered StructType

  # ARRAY: List
  tags:
    type: ARRAY
    itemType: STRING
    constraints:
      maxItems: 20
      uniqueItems: true           # No duplicate elements

  # MAP: Key-value mapping
  metadata:
    type: MAP
    keyType: STRING
    valueType: STRING
    constraints:
      maxEntries: 50

  # REFERENCE: Reference to another ObjectType
  ownerId:
    type: REFERENCE
    targetType: User
    onDelete: SET_NULL            # Cascade strategy

  # ATTACHMENT: File attachment
  documents:
    type: ARRAY
    itemType: ATTACHMENT
    constraints:
      maxFileSize: "10MB"
      allowedMimeTypes:
        - "application/pdf"
        - "image/*"

#2.4 Special Types

YAML
properties:
  # GEO_POINT: Geographic coordinate
  location:
    type: GEO_POINT
    constraints:
      srid: 4326                  # WGS84 coordinate system

  # GEO_SHAPE: Geographic shape
  boundary:
    type: GEO_SHAPE
    constraints:
      allowedShapes: [POLYGON, MULTI_POLYGON]

  # VECTOR: Vector embedding
  embedding:
    type: VECTOR
    constraints:
      dimensions: 768             # Vector dimensions
      similarity: COSINE          # Similarity calculation method

  # TIMESERIES: Time series data reference
  temperatureHistory:
    type: TIMESERIES
    valueType: DOUBLE
    resolution: "1m"              # 1-minute resolution

  # EXPRESSION: Computed expression
  displayLabel:
    type: EXPRESSION
    expression: "concat(name, ' (', code, ')')"

#2.5 Complete Type Hierarchy

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. Constraint System Deep Dive

#3.1 Field-Level Constraints

YAML
properties:
  email:
    type: STRING
    required: true                    # Not-null constraint
    unique: true                      # Uniqueness constraint
    immutable: false                  # Mutable after creation
    constraints:
      pattern: "^[\\w.-]+@[\\w.-]+\\.\\w+$"  # Regex constraint
      maxLength: 255

  age:
    type: INTEGER
    constraints:
      min: 0                          # Minimum value
      max: 200                        # Maximum value

  score:
    type: DOUBLE
    constraints:
      min: 0.0
      max: 100.0
      multipleOf: 0.5                 # Must be multiple of 0.5

  startDate:
    type: DATE
    constraints:
      futureOnly: true                # Only future dates allowed

Constraint Quick Reference:

ConstraintApplicable TypesDescription
requiredAllNot null
uniqueAll scalarsGlobally unique
immutableAllCannot change after creation
defaultValueAllDefault value
min / maxNumeric, dateRange limit
minLength / maxLengthSTRINGLength limit
patternSTRINGRegex match
multipleOfNumericMultiple constraint
futureOnly / pastOnlyDATE, TIMESTAMPTime direction
maxItems / minItemsARRAYArray length
uniqueItemsARRAYElement uniqueness
maxEntriesMAPMap entry count
allowedMimeTypesATTACHMENTFile type whitelist
maxFileSizeATTACHMENTFile size limit

#3.2 Object-Level Constraints

Cross-field business rules:

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

  constraints:
    # Cross-field constraint
    - name: dateRange
      type: COMPARISON
      expression: "startDate <= endDate"
      message: "End date must be after start date"

    - name: quantityRange
      type: COMPARISON
      expression: "minQuantity <= maxQuantity"
      message: "Max quantity must be >= min quantity"

    # Conditional required
    - name: conditionalRequired
      type: CONDITIONAL
      when: "status == 'ACTIVE'"
      then:
        required: ["activatedBy", "activatedAt"]
      message: "Activated status requires activator and activation time"

    # Mutual exclusion
    - name: mutualExclusive
      type: MUTUAL_EXCLUSIVE
      fields: ["internalCode", "externalCode"]
      message: "Internal and external code cannot both be empty"

    # Custom rule
    - name: luhnCheck
      type: CUSTOM
      expression: "luhn_check(cardNumber)"
      message: "Card number checksum is incorrect"

#3.3 Relation-Level Constraints

YAML
# Referential integrity
relations:
  - name: OrderBelongsToCustomer
    from: Order
    to: Customer
    cardinality: MANY_TO_ONE
    constraints:
      required: true                  # Every Order must have a Customer
      onDelete: RESTRICT              # Cannot delete Customer with Orders
      onUpdate: CASCADE               # Cascade on Customer PK change

# Cardinality constraints
  - name: TeamHasMembers
    from: Team
    to: Employee
    cardinality: ONE_TO_MANY
    constraints:
      minCount: 1                     # Team must have at least 1 member
      maxCount: 50                    # Team can have at most 50 members

#4. Primary Key Design Strategies

#4.1 Four Strategies Compared

Code
┌──────────────┬─────────────┬────────────┬────────────┐
│   Strategy    │   Example    │   Pros      │   Cons     │
├──────────────┼─────────────┼────────────┼────────────┤
│ Natural Key  │ email       │ Readable    │ May change  │
│              │ isbn        │ No gen needed│ Data skew  │
├──────────────┼─────────────┼────────────┼────────────┤
│ UUID         │ uuid-v4     │ Globally    │ Index bloat │
│              │             │ unique      │ Not readable│
├──────────────┼─────────────┼────────────┼────────────┤
│ Composite    │ (tenant,id) │ Natural     │ Complex     │
│              │             │ partitioning│ queries     │
├──────────────┼─────────────┼────────────┼────────────┤
│ Sequence     │ BIGINT auto │ Compact     │ Not distrib │
│              │             │ Ordered     │ Info leak   │
└──────────────┴─────────────┴────────────┴────────────┘

#4.2 coomia-dip Recommendation: Semantic ID

YAML
spec:
  primaryKey:
    property: equipmentId
    strategy: SEMANTIC_ID
    config:
      prefix: "EQ"                    # Type prefix
      separator: "-"
      segments:
        - type: PROPERTY
          source: factoryCode         # From factory code
          length: 3
        - type: SEQUENCE
          length: 6                   # 6-digit sequence
      # Result: EQ-BJ1-000001, EQ-SH2-000001

Decision Matrix:

Code
                           Distributed-Friendly
                    Low ◄──────────────► High
                    │                    │
            High    │  Sequence          │  UUID
        Perf  │     │  (fast single-node)│  (distributed)
              │     │                    │
              │     │                    │
            Low     │  Natural Key       │  Composite
                    │  (needs stability) │  (multi-tenant)
                    │                    │
                    └────────────────────┘

  coomia-dip recommends: Semantic ID (balances readability + distribution)

#4.3 Primary Key Configuration Examples

Python
from ontology_sdk import ObjectTypeSpec, PrimaryKeyConfig

# Strategy 1: UUID (default, safest)
order = ObjectTypeSpec(
    name="Order",
    primary_key=PrimaryKeyConfig(
        property="orderId",
        strategy="UUID_V4"
    )
)

# Strategy 2: Semantic ID (recommended)
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}
        ]
    )
)

# Strategy 3: Composite key (multi-tenant scenarios)
tenant_resource = ObjectTypeSpec(
    name="TenantResource",
    primary_key=PrimaryKeyConfig(
        properties=["tenantId", "resourceId"],
        strategy="COMPOSITE"
    )
)

# Strategy 4: Natural key (stable business identifiers)
country = ObjectTypeSpec(
    name="Country",
    primary_key=PrimaryKeyConfig(
        property="isoCode",
        strategy="NATURAL",
        immutable=True
    )
)

#5. Advanced Property Features

#5.1 Derived Properties

YAML
properties:
  # Expression-derived
  fullName:
    type: STRING
    derived: true
    expression: "concat(firstName, ' ', lastName)"

  # Aggregation-derived (cross-relation)
  totalOrderAmount:
    type: DECIMAL
    derived: true
    aggregation:
      type: SUM
      relation: CustomerPlacedOrder
      property: amount

  # Conditional-derived
  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         # Optimistic lock version

#5.3 Sensitive Properties

YAML
properties:
  ssn:
    type: STRING
    sensitive: true
    masking:
      strategy: PARTIAL             # Partial masking
      visibleChars: 4               # Show last 4 characters
      maskChar: "*"
      # Display: ***-**-1234
    encryption:
      algorithm: AES_256_GCM
      keyId: "key-ssn-001"
    access:
      requiredPermission: "view:pii"

#6. Type System Runtime Behavior

#6.1 Type Conversion Rules

Code
┌─────────────────────────────────────────────────┐
│           Safe Type Conversion Matrix            │
├──────────┬──────────────────────────────────────┤
│ Source    │ Can safely convert to                │
├──────────┼──────────────────────────────────────┤
│ INTEGER  │ LONG, DOUBLE, DECIMAL, STRING        │
│ LONG     │ DOUBLE, DECIMAL, STRING              │
│ DOUBLE   │ STRING                               │
│ DECIMAL  │ STRING                               │
│ BOOLEAN  │ STRING, INTEGER(0/1)                 │
│ DATE     │ TIMESTAMP, STRING                    │
│ STRING   │ (requires explicit parse, no auto)   │
└──────────┴──────────────────────────────────────┘

Safe conversion = no information loss
Unsafe conversions require explicit CAST

#6.2 NULL Value Handling

YAML
# coomia-dip NULL semantics
properties:
  middleName:
    type: STRING
    required: false               # NULL allowed
    nullSemantics: ABSENT         # NULL = value doesn't exist (default)

  deletedAt:
    type: TIMESTAMP
    required: false
    nullSemantics: NOT_APPLICABLE  # NULL = not applicable (not deleted)

  score:
    type: DOUBLE
    required: false
    nullSemantics: UNKNOWN         # NULL = value unknown
    defaultOnNull: 0.0             # Treat NULL as 0.0 in queries

#7. Practical Example: Designing a Complete ObjectType

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Product
  namespace: ecommerce
  version: "1.0.0"
spec:
  displayName: "Product"
  description: "E-commerce platform product entity"

  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                # Full-text search support

    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

    # Derived properties
    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: "Product price must be greater than zero"

  audit:
    enabled: true

  interfaces:
    - Searchable
    - Auditable
    - SoftDeletable

  lifecycle: DRAFT

#8. Property Type Selection Decision Tree

Code
What type of data do you need to store?
│
├── Single value
│   ├── Text → STRING
│   ├── Integer → INTEGER (< 2^31) or LONG
│   ├── Decimal
│   │   ├── Exact calculation (money) → DECIMAL
│   │   └── Approximate OK (scientific) → DOUBLE
│   ├── Yes/No → BOOLEAN
│   ├── Time
│   │   ├── Date only → DATE
│   │   ├── Time only → TIME
│   │   ├── Date+Time → TIMESTAMP
│   │   └── Duration → DURATION
│   ├── Finite options → ENUM
│   └── Reference to another entity → REFERENCE
│
├── Structured value
│   ├── Fixed structure → STRUCT
│   ├── List → ARRAY
│   └── Key-value pairs → MAP
│
├── Special value
│   ├── Geographic location → GEO_POINT / GEO_SHAPE
│   ├── Vector embedding → VECTOR
│   ├── Time series data → TIMESERIES
│   ├── Files → ATTACHMENT
│   └── Computed expression → EXPRESSION
│
└── Computed from other properties → Any type + derived: true

#9. Performance Considerations

#9.1 Indexing Strategies

YAML
spec:
  indexes:
    # Single-field index
    - name: idx_product_sku
      properties: [sku]
      type: UNIQUE

    # Composite index
    - name: idx_product_category_price
      properties: [category, price]
      type: BTREE

    # Full-text search index
    - name: idx_product_name_fts
      properties: [name]
      type: FULLTEXT
      analyzer: "standard"

    # Vector index
    - name: idx_product_embedding
      properties: [embedding]
      type: HNSW
      config:
        m: 16
        efConstruction: 200

#9.2 Storage Optimization Recommendations

ScenarioRecommendationReason
High-cardinality STRINGSet maxLengthAvoid VARCHAR(MAX)
Frequently queried ENUMIndex + materializeAvoid full table scan
Large ARRAYConsider separate ObjectTypeAvoid document bloat
Large MAPConsider separate ObjectTypeAvoid key explosion
ATTACHMENTSeparate storage (MinIO)Avoid DB bloat
TIMESERIESDedicated time-series tableWrite optimization

#10. Common Mistakes and Best Practices

#Mistake 1: Overusing STRING

YAML
# Wrong
status:
  type: STRING        # Can store anything

# Correct
status:
  type: ENUM
  enumValues: [ACTIVE, INACTIVE, SUSPENDED]

#Mistake 2: Ignoring Precision Needs

YAML
# Wrong: Using DOUBLE for money has floating-point precision issues
amount:
  type: DOUBLE

# Correct: Use DECIMAL for money
amount:
  type: DECIMAL
  constraints:
    precision: 10
    scale: 2

#Mistake 3: Excessive Nesting

YAML
# Wrong: 3-level nesting, hard to query
address:
  type: STRUCT
  structType: Address    # Address nests City, City nests Country

# Correct: Flatten + reference
city:
  type: REFERENCE
  targetType: City
address:
  type: STRUCT
  structType: SimpleAddress   # Only contains street, zipCode

#Mistake 4: Poor Primary Key Choice

YAML
# Wrong: Email as PK (may change)
primaryKey: email

# Wrong: Pure auto-increment (not distributed)
primaryKey:
  strategy: SEQUENCE

# Correct: Semantic ID
primaryKey:
  property: customerId
  strategy: SEMANTIC_ID
  prefix: "CUS"

#Key Takeaways

  1. Choosing the right property type is the first step in Schema design. 20+ types cover everything from simple scalars to geospatial and vector embeddings. Prefer the most precise type (ENUM over STRING, DECIMAL over DOUBLE) and let the type system validate for you.

  2. Constraints are the first line of defense for data quality. The three-layer constraint system (field → object → relation) pushes business rules down to the Schema layer, avoiding the classic "inconsistent application-layer validation" problem.

  3. Primary key design has far-reaching impact. The Semantic ID approach balances readability, distributed-friendliness, and type identification — it's coomia-dip's recommended strategy. Avoid using mutable business fields as primary keys.

#Next Article

S4-03: ObjectType Lifecycle: The State Machine from DRAFT to ARCHIVED — We'll dive into ObjectType's 4 lifecycle states, transition rules, and compatibility checks.

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