Back to Blog

Ontology Practice: E-Commerce Modeling

A mid-size e-commerce platform typically involves these core business entities:

CoomiaPublished on August 19, 202515 min read
Share this articleTwitter / X

Ontology Practice: E-Commerce Modeling

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

#TL;DR

  • E-commerce platforms are a classic training ground for Ontology modeling — Product, Order, User, Inventory, and Logistics — five core ObjectTypes covering the complete business chain from product listing to delivery, demonstrating the full modeling capabilities of the Ontology five-tuple.
  • Derived properties eliminate the pain of "metrics scattered in code" — product sales volume, user LTV, inventory turnover rate, and other key metrics are automatically computed through declarative definitions, without writing additional ETL or statistics SQL.
  • ActionType standardizes business operations — PlaceOrder, Pay, Ship, and Refund — four core operations declared via ActionType, with the platform automatically handling idempotency, permissions, and auditing, letting developers focus on business logic.

#1. Introduction: Modeling Challenges in E-Commerce

A mid-size e-commerce platform typically involves these core business entities:

Code
┌─────────────────────────────────────────────────────────┐
│                E-Commerce Business Panorama              │
│                                                         │
│  ┌──────┐    ┌──────┐    ┌──────────┐    ┌──────────┐  │
│  │ User  │───►│ Order │───►│ Shipment │───►│ Delivery │  │
│  └──┬───┘    └──┬───┘    └──────────┘    └──────────┘  │
│     │           │                                      │
│     │      ┌────┴─────┐                                │
│     │      │OrderItem  │                                │
│     │      └────┬─────┘                                │
│     │           │                                      │
│     │      ┌────┴─────┐   ┌──────────┐  ┌──────────┐  │
│     └─────►│ Product   │◄──│Inventory │◄──│Warehouse │  │
│            └────┬─────┘   └──────────┘  └──────────┘  │
│                 │                                      │
│            ┌────┴─────┐   ┌──────────┐                │
│            │ Category  │   │  Brand   │                │
│            └──────────┘   └──────────┘                │
└─────────────────────────────────────────────────────────┘

Traditional ER modeling can only describe the data structure of these entities, while Ontology modeling must also answer:

  • How is user lifetime value (LTV) computed in real time?
  • What preconditions must the "PlaceOrder" operation check?
  • How to auto-trigger procurement when inventory is low?
  • How to query all "searchable" objects uniformly across types?

#2. ObjectType Definitions

#2.1 User

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: User
  displayName: User
spec:
  primaryKey: userId
  implements:
    - Auditable
    - Taggable
  properties:
    userId:
      type: STRING
      required: true
      description: Unique user identifier
    username:
      type: STRING
      required: true
      constraints:
        minLength: 3
        maxLength: 32
        pattern: "^[a-zA-Z0-9_]+$"
    email:
      type: STRING
      constraints:
        pattern: "^[\\w.-]+@[\\w.-]+\\.\\w+$"
    phone:
      type: STRING
    displayName:
      type: STRING
    avatarUrl:
      type: STRING
    memberLevel:
      type: STRING
      enum: [REGULAR, SILVER, GOLD, PLATINUM, DIAMOND]
      default: REGULAR
    shippingAddresses:
      type: ARRAY
      itemType: STRUCT
      structType: Address
    isActive:
      type: BOOLEAN
      default: true

    # Derived properties
    totalOrders:
      type: INTEGER
      derived: true
      aggregation: COUNT
      sourceRelation: PlacedOrder
    totalSpent:
      type: DOUBLE
      derived: true
      aggregation: SUM
      sourceRelation: PlacedOrder
      sourceProperty: totalAmount
    averageOrderValue:
      type: DOUBLE
      derived: true
      expression: "totalSpent / NULLIF(totalOrders, 0)"
    daysSinceLastOrder:
      type: INTEGER
      derived: true
      expression: "DATEDIFF(NOW(), lastOrderDate)"
    lifetimeValue:
      type: DOUBLE
      derived: true
      expression: "totalSpent * (1 + repeatRate * 0.5)"

#2.2 Product

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Product
  displayName: Product
spec:
  primaryKey: productId
  implements:
    - Auditable
    - Taggable
    - Searchable
  properties:
    productId:
      type: STRING
      required: true
    sku:
      type: STRING
      required: true
      constraints:
        unique: true
    name:
      type: STRING
      required: true
      searchable: true
    description:
      type: STRING
      searchable: true
    mainImageUrl:
      type: STRING
    imageUrls:
      type: ARRAY
      itemType: STRING
    basePrice:
      type: DOUBLE
      required: true
      constraints:
        min: 0.01
    currentPrice:
      type: DOUBLE
      constraints:
        min: 0.01
    costPrice:
      type: DOUBLE
    currency:
      type: STRING
      default: USD
    weight:
      type: DOUBLE
      description: Weight in kg
    dimensions:
      type: STRUCT
      structType: Dimensions
    status:
      type: STRING
      enum: [DRAFT, ACTIVE, OUT_OF_STOCK, DISCONTINUED]
      default: DRAFT
    isOnSale:
      type: BOOLEAN
      default: false
    salePrice:
      type: DOUBLE

    # Derived properties
    totalSold:
      type: INTEGER
      derived: true
      aggregation: SUM
      sourceRelation: ContainedInOrderItem
      sourceProperty: quantity
    totalRevenue:
      type: DOUBLE
      derived: true
      aggregation: SUM
      sourceRelation: ContainedInOrderItem
      sourceProperty: subtotal
    averageRating:
      type: DOUBLE
      derived: true
      aggregation: AVG
      sourceRelation: HasReview
      sourceProperty: rating
    reviewCount:
      type: INTEGER
      derived: true
      aggregation: COUNT
      sourceRelation: HasReview
    grossMargin:
      type: DOUBLE
      derived: true
      expression: "(currentPrice - costPrice) / currentPrice"
    availableStock:
      type: INTEGER
      derived: true
      aggregation: SUM
      sourceRelation: StoredIn
      sourceProperty: availableQuantity

#2.3 Order

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Order
  displayName: Order
spec:
  primaryKey: orderId
  implements:
    - Auditable
    - Statusable
  properties:
    orderId:
      type: STRING
      required: true
    orderNumber:
      type: STRING
      required: true
      constraints:
        unique: true
    status:
      type: STRING
      enum: [PENDING, PAID, PROCESSING, SHIPPED, DELIVERED, COMPLETED,
             CANCELLED, REFUNDING, REFUNDED]
      default: PENDING
    totalAmount:
      type: DOUBLE
      required: true
    discountAmount:
      type: DOUBLE
      default: 0
    shippingFee:
      type: DOUBLE
      default: 0
    payableAmount:
      type: DOUBLE
      derived: true
      expression: "totalAmount - discountAmount + shippingFee"
    paymentMethod:
      type: STRING
      enum: [ALIPAY, WECHAT_PAY, CREDIT_CARD, BANK_TRANSFER, PAYPAL, STRIPE]
    paidAt:
      type: TIMESTAMP
    shippedAt:
      type: TIMESTAMP
    deliveredAt:
      type: TIMESTAMP
    completedAt:
      type: TIMESTAMP
    cancelledAt:
      type: TIMESTAMP
    cancelReason:
      type: STRING
    shippingAddress:
      type: STRUCT
      structType: Address
    note:
      type: STRING
      constraints:
        maxLength: 500

    # Derived properties
    itemCount:
      type: INTEGER
      derived: true
      aggregation: COUNT
      sourceRelation: ContainsItem
    totalQuantity:
      type: INTEGER
      derived: true
      aggregation: SUM
      sourceRelation: ContainsItem
      sourceProperty: quantity
    processingDays:
      type: INTEGER
      derived: true
      expression: "DATEDIFF(shippedAt, paidAt)"
    deliveryDays:
      type: INTEGER
      derived: true
      expression: "DATEDIFF(deliveredAt, shippedAt)"

#2.4 OrderItem

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: OrderItem
  displayName: Order Item
spec:
  primaryKey: itemId
  properties:
    itemId:
      type: STRING
      required: true
    quantity:
      type: INTEGER
      required: true
      constraints:
        min: 1
    unitPrice:
      type: DOUBLE
      required: true
    subtotal:
      type: DOUBLE
      derived: true
      expression: "quantity * unitPrice"
    discountAmount:
      type: DOUBLE
      default: 0
    finalPrice:
      type: DOUBLE
      derived: true
      expression: "subtotal - discountAmount"
    productSnapshot:
      type: STRUCT
      structType: ProductSnapshot
      description: Product snapshot at order time, prevents product changes from affecting historical orders

#2.5 Inventory

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Inventory
  displayName: Inventory
spec:
  primaryKey: inventoryId
  implements:
    - Auditable
    - Measurable
  properties:
    inventoryId:
      type: STRING
      required: true
    totalQuantity:
      type: INTEGER
      required: true
      constraints:
        min: 0
    availableQuantity:
      type: INTEGER
      derived: true
      expression: "totalQuantity - reservedQuantity - damagedQuantity"
    reservedQuantity:
      type: INTEGER
      default: 0
      description: Quantity locked by orders but not yet shipped
    damagedQuantity:
      type: INTEGER
      default: 0
    reorderPoint:
      type: INTEGER
      default: 10
      description: Safety stock threshold
    maxQuantity:
      type: INTEGER
      description: Maximum stock level
    isLowStock:
      type: BOOLEAN
      derived: true
      expression: "availableQuantity <= reorderPoint"
    turnoverRate:
      type: DOUBLE
      derived: true
      expression: "soldLast30Days / AVG(availableQuantity)"
      description: Inventory turnover rate
    daysOfSupply:
      type: DOUBLE
      derived: true
      expression: "availableQuantity / (soldLast30Days / 30)"
      description: Days of supply at current sales velocity

#2.6 Shipment

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Shipment
  displayName: Shipment
spec:
  primaryKey: shipmentId
  implements:
    - Auditable
    - Statusable
    - Locatable
  properties:
    shipmentId:
      type: STRING
      required: true
    trackingNumber:
      type: STRING
      constraints:
        unique: true
    carrier:
      type: STRING
      enum: [FEDEX, UPS, DHL, USPS, SF_EXPRESS, LOCAL_COURIER]
    status:
      type: STRING
      enum: [PENDING, PICKED_UP, IN_TRANSIT, OUT_FOR_DELIVERY,
             DELIVERED, RETURNED, LOST]
      default: PENDING
    estimatedDeliveryDate:
      type: DATE
    actualDeliveryDate:
      type: DATE
    originAddress:
      type: STRUCT
      structType: Address
    destinationAddress:
      type: STRUCT
      structType: Address
    weight:
      type: DOUBLE
    shippingCost:
      type: DOUBLE
    trackingEvents:
      type: ARRAY
      itemType: STRUCT
      structType: TrackingEvent
    isDelayed:
      type: BOOLEAN
      derived: true
      expression: "NOW() > estimatedDeliveryDate AND status != 'DELIVERED'"

#3. StructType Definitions

YAML
# Address structure
kind: StructType
metadata:
  name: Address
spec:
  properties:
    street: { type: STRING }
    city: { type: STRING }
    state: { type: STRING }
    country: { type: STRING }
    postalCode: { type: STRING }
    receiverName: { type: STRING }
    receiverPhone: { type: STRING }

---
# Product dimensions
kind: StructType
metadata:
  name: Dimensions
spec:
  properties:
    length: { type: DOUBLE, description: "Length in cm" }
    width: { type: DOUBLE, description: "Width in cm" }
    height: { type: DOUBLE, description: "Height in cm" }

---
# Product snapshot (frozen product info in order items)
kind: StructType
metadata:
  name: ProductSnapshot
spec:
  properties:
    productId: { type: STRING }
    name: { type: STRING }
    sku: { type: STRING }
    imageUrl: { type: STRING }
    price: { type: DOUBLE }

---
# Shipping tracking event
kind: StructType
metadata:
  name: TrackingEvent
spec:
  properties:
    timestamp: { type: TIMESTAMP }
    location: { type: STRING }
    description: { type: STRING }
    status: { type: STRING }

#4. InterfaceType Definitions

YAML
# Searchable interface
kind: InterfaceType
metadata:
  name: Searchable
spec:
  properties:
    searchableText:
      type: STRING
      description: Full-text search index field
    searchRank:
      type: DOUBLE
      description: Search ranking weight
  implementedBy:
    - Product
    - Category
    - Brand

---
# Reviewable interface
kind: InterfaceType
metadata:
  name: Reviewable
spec:
  properties:
    averageRating:
      type: DOUBLE
    reviewCount:
      type: INTEGER
    latestReviewAt:
      type: TIMESTAMP
  implementedBy:
    - Product
    - Shipment

#5. RelationType Definitions

Python
from ontology_sdk import RelationType

# Core relation definitions
relations = [
    # User -> Order
    RelationType(
        name="PlacedOrder",
        source="User",
        target="Order",
        cardinality="ONE_TO_MANY",
        description="User places order",
    ),

    # Order -> OrderItem
    RelationType(
        name="ContainsItem",
        source="Order",
        target="OrderItem",
        cardinality="ONE_TO_MANY",
        description="Order contains product items",
        cascade_delete=True,
    ),

    # OrderItem -> Product
    RelationType(
        name="RefersToProduct",
        source="OrderItem",
        target="Product",
        cardinality="MANY_TO_ONE",
        description="Order item references product",
    ),

    # Product -> Category
    RelationType(
        name="BelongsToCategory",
        source="Product",
        target="Category",
        cardinality="MANY_TO_ONE",
        description="Product belongs to category",
    ),

    # Product -> Brand
    RelationType(
        name="ProducedByBrand",
        source="Product",
        target="Brand",
        cardinality="MANY_TO_ONE",
        description="Product belongs to brand",
    ),

    # Product -> Inventory (via warehouse)
    RelationType(
        name="StoredIn",
        source="Product",
        target="Inventory",
        cardinality="ONE_TO_MANY",
        properties={
            "warehouseId": "STRING",
        },
        description="Product inventory across warehouses",
    ),

    # Inventory -> Warehouse
    RelationType(
        name="LocatedInWarehouse",
        source="Inventory",
        target="Warehouse",
        cardinality="MANY_TO_ONE",
        description="Inventory location",
    ),

    # Order -> Shipment
    RelationType(
        name="ShippedVia",
        source="Order",
        target="Shipment",
        cardinality="ONE_TO_ONE",
        description="Order shipping information",
    ),

    # User -> Product (browsing)
    RelationType(
        name="Viewed",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "viewedAt": "TIMESTAMP",
            "duration": "INTEGER",
        },
        description="User product browsing history",
    ),

    # User -> Product (favorites)
    RelationType(
        name="Favorited",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "favoritedAt": "TIMESTAMP",
        },
        description="User product favorites",
    ),

    # User -> Product (reviews)
    RelationType(
        name="ReviewedProduct",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "rating": "INTEGER",
            "comment": "STRING",
            "reviewedAt": "TIMESTAMP",
            "images": "ARRAY[STRING]",
        },
        description="User product reviews",
    ),
]

Relation graph:

Code
                    ┌──────────┐
                    │   User   │
                    └────┬─────┘
                         │
         ┌───────────────┼────────────────┐
         │ PlacedOrder   │ Favorited      │ Viewed
         ▼               ▼                ▼
    ┌────────┐     ┌──────────┐     ┌──────────┐
    │  Order  │     │ Product  │◄────│ Category │
    └────┬───┘     └────┬─────┘     └──────────┘
         │              │
    ContainsItem    StoredIn         ProducedByBrand
         │              │                  │
         ▼              ▼                  ▼
    ┌──────────┐  ┌──────────┐      ┌──────────┐
    │OrderItem │  │Inventory │      │  Brand   │
    └────┬─────┘  └────┬─────┘      └──────────┘
         │              │
    RefersToProduct  LocatedIn
         │              │
         ▼              ▼
    ┌──────────┐  ┌──────────┐
    │ Product  │  │Warehouse │
    └──────────┘  └──────────┘

    Order ──ShippedVia──► Shipment

#6. ActionType Definitions

#6.1 Place Order

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: PlaceOrder
  displayName: Place Order
spec:
  description: User submits an order
  objectType: Order
  executor: FUNCTION
  parameters:
    userId:
      type: STRING
      required: true
    items:
      type: ARRAY
      itemType: STRUCT
      structType: OrderItemInput
      constraints:
        minItems: 1
        maxItems: 100
    shippingAddress:
      type: STRUCT
      structType: Address
      required: true
    couponCode:
      type: STRING
    note:
      type: STRING
  preconditions:
    - name: user_active
      expression: "User[userId].isActive == true"
      errorMessage: User account is disabled
    - name: items_in_stock
      expression: "ALL(items, item => Product[item.productId].availableStock >= item.quantity)"
      errorMessage: Some items are out of stock
    - name: items_on_sale
      expression: "ALL(items, item => Product[item.productId].status == 'ACTIVE')"
      errorMessage: Some items have been delisted
  sideEffects:
    - type: CREATE
      target: Order
    - type: CREATE
      target: OrderItem
      foreach: items
    - type: UPDATE
      target: Inventory
      property: reservedQuantity
      expression: "reservedQuantity + item.quantity"
  idempotency:
    key: "userId + hash(items) + timestamp(5min)"
    strategy: REJECT_DUPLICATE
  permissions:
    - role: CUSTOMER
      condition: "userId == currentUser.id"
  audit:
    level: FULL
    includeParameters: true

#6.2 Pay Order

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: PayOrder
  displayName: Pay Order
spec:
  objectType: Order
  executor: FUNCTION
  parameters:
    orderId:
      type: STRING
      required: true
    paymentMethod:
      type: STRING
      required: true
      enum: [ALIPAY, WECHAT_PAY, CREDIT_CARD, BANK_TRANSFER, PAYPAL, STRIPE]
    paymentToken:
      type: STRING
      required: true
  preconditions:
    - name: order_pending
      expression: "Order[orderId].status == 'PENDING'"
      errorMessage: Order status does not allow payment
    - name: not_expired
      expression: "DATEDIFF(NOW(), Order[orderId].createdAt) < 30"
      errorMessage: Order has expired, please place a new order
  sideEffects:
    - type: UPDATE
      target: Order
      properties:
        status: PAID
        paymentMethod: parameters.paymentMethod
        paidAt: NOW()
  timeout: 30s
  retryPolicy:
    maxRetries: 3
    backoff: EXPONENTIAL

#6.3 Ship Order

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: ShipOrder
  displayName: Ship Order
spec:
  objectType: Order
  executor: FUNCTION
  parameters:
    orderId:
      type: STRING
      required: true
    carrier:
      type: STRING
      required: true
    trackingNumber:
      type: STRING
      required: true
    warehouseId:
      type: STRING
      required: true
  preconditions:
    - name: order_paid
      expression: "Order[orderId].status == 'PAID' OR Order[orderId].status == 'PROCESSING'"
  sideEffects:
    - type: UPDATE
      target: Order
      properties:
        status: SHIPPED
        shippedAt: NOW()
    - type: CREATE
      target: Shipment
    - type: UPDATE
      target: Inventory
      property: totalQuantity
      expression: "totalQuantity - orderItem.quantity"
    - type: UPDATE
      target: Inventory
      property: reservedQuantity
      expression: "reservedQuantity - orderItem.quantity"
  permissions:
    - role: WAREHOUSE_OPERATOR
    - role: ADMIN

#6.4 Refund Order

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: RefundOrder
  displayName: Refund Order
spec:
  objectType: Order
  executor: APPROVAL_THEN_FUNCTION
  parameters:
    orderId:
      type: STRING
      required: true
    reason:
      type: STRING
      required: true
      enum: [QUALITY_ISSUE, WRONG_ITEM, NOT_AS_DESCRIBED, CHANGED_MIND, OTHER]
    refundAmount:
      type: DOUBLE
      required: true
    evidence:
      type: ARRAY
      itemType: STRING
      description: Refund evidence image URLs
  preconditions:
    - name: refundable_status
      expression: "Order[orderId].status IN ['PAID', 'SHIPPED', 'DELIVERED']"
    - name: within_refund_window
      expression: "DATEDIFF(NOW(), Order[orderId].completedAt) <= 7"
      errorMessage: Refund window of 7 days has expired
  approval:
    approvers:
      - role: CUSTOMER_SERVICE
    timeout: 48h
    autoApproveCondition: "refundAmount < 100 AND reason != 'CHANGED_MIND'"
  sideEffects:
    - type: UPDATE
      target: Order
      properties:
        status: REFUNDED
    - type: UPDATE
      target: Inventory
      property: totalQuantity
      expression: "totalQuantity + returnedQuantity"
  permissions:
    - role: CUSTOMER
      condition: "Order[orderId].userId == currentUser.id"

#7. Metric Design

Python
# Product metrics
product_metrics = [
    MetricSpec(
        name="ProductConversionRate",
        description="Product view-to-purchase conversion rate",
        expression="totalSold / viewCount",
        dimensions=["category", "brand"],
        unit="percentage",
    ),
    MetricSpec(
        name="ProductGMV",
        description="Product gross merchandise value",
        object_type="Product",
        property="totalRevenue",
        aggregation="SUM",
        dimensions=["category", "brand"],
        time_series=True,
        unit="USD",
    ),
]

# Order metrics
order_metrics = [
    MetricSpec(
        name="OrderAverageValue",
        description="Average order value",
        object_type="Order",
        property="payableAmount",
        aggregation="AVG",
        filter="status NOT IN ['CANCELLED', 'REFUNDED']",
        dimensions=["paymentMethod"],
        time_series=True,
        unit="USD",
    ),
    MetricSpec(
        name="OrderFulfillmentRate",
        description="Order fulfillment rate",
        expression="COUNT(status='COMPLETED') / COUNT(status='PAID')",
        dimensions=["warehouse"],
        unit="percentage",
    ),
    MetricSpec(
        name="AverageDeliveryDays",
        description="Average delivery days",
        object_type="Order",
        property="deliveryDays",
        aggregation="AVG",
        filter="status = 'COMPLETED'",
        dimensions=["carrier", "region"],
        unit="days",
    ),
]

# User metrics
user_metrics = [
    MetricSpec(
        name="UserRetentionRate",
        description="User retention rate (30 days)",
        expression="COUNT(hasOrderInLast30Days) / COUNT(ALL)",
        dimensions=["memberLevel", "registrationChannel"],
        unit="percentage",
    ),
    MetricSpec(
        name="UserLifetimeValue",
        description="User lifetime value",
        object_type="User",
        property="lifetimeValue",
        aggregation="AVG",
        dimensions=["memberLevel"],
        unit="USD",
    ),
]

# Inventory metrics
inventory_metrics = [
    MetricSpec(
        name="InventoryTurnoverRate",
        description="Inventory turnover rate",
        object_type="Inventory",
        property="turnoverRate",
        aggregation="AVG",
        dimensions=["warehouse", "category"],
        alert_rules=[
            AlertRule("turnover-low", "value < 2", "WARNING",
                     "Warehouse {warehouse} category {category} turnover below 2"),
        ],
    ),
    MetricSpec(
        name="LowStockItems",
        description="Low stock item count",
        object_type="Inventory",
        filter="isLowStock == true",
        aggregation="COUNT",
        dimensions=["warehouse"],
        alert_rules=[
            AlertRule("low-stock-critical", "value > 50", "CRITICAL",
                     "Warehouse {warehouse} has {value} low stock items"),
        ],
    ),
]

#8. Complete Code Example

#8.1 Creating the E-Commerce Ontology with SDK

Python
from ontology_sdk import OntologyClient, ObjectTypeSpec, PropertySpec

client = OntologyClient(
    base_url="http://control-Layer:8080",
    project_id="ecommerce-platform",
)

# Create StructTypes
client.schema.create_struct_type(StructTypeSpec(
    name="Address",
    properties={
        "street": PropertySpec(type="STRING"),
        "city": PropertySpec(type="STRING"),
        "state": PropertySpec(type="STRING"),
        "country": PropertySpec(type="STRING"),
        "postalCode": PropertySpec(type="STRING"),
        "receiverName": PropertySpec(type="STRING"),
        "receiverPhone": PropertySpec(type="STRING"),
    },
))

# Create InterfaceTypes
client.schema.create_interface_type(InterfaceTypeSpec(
    name="Searchable",
    properties={
        "searchableText": PropertySpec(type="STRING"),
        "searchRank": PropertySpec(type="DOUBLE"),
    },
))

# Create ObjectType: User
client.schema.create_object_type(ObjectTypeSpec(
    name="User",
    display_name="User",
    primary_key="userId",
    implements=["Auditable", "Taggable"],
    properties={
        "userId": PropertySpec(type="STRING", required=True),
        "username": PropertySpec(type="STRING", required=True),
        "email": PropertySpec(type="STRING"),
        "memberLevel": PropertySpec(
            type="STRING",
            enum=["REGULAR", "SILVER", "GOLD", "PLATINUM", "DIAMOND"],
            default="REGULAR",
        ),
        "isActive": PropertySpec(type="BOOLEAN", default=True),
        "totalOrders": PropertySpec(
            type="INTEGER", derived=True,
            aggregation="COUNT", source_relation="PlacedOrder",
        ),
        "lifetimeValue": PropertySpec(
            type="DOUBLE", derived=True,
            expression="totalSpent * (1 + repeatRate * 0.5)",
        ),
    },
))

# Create remaining ObjectTypes...
# (Product, Order, OrderItem, Inventory, Shipment)

# Create RelationTypes
client.schema.create_relation_type(RelationTypeSpec(
    name="PlacedOrder",
    source="User",
    target="Order",
    cardinality="ONE_TO_MANY",
))

# Create ActionTypes
client.schema.create_action_type(ActionTypeSpec(
    name="PlaceOrder",
    object_type="Order",
    executor="FUNCTION",
    parameters={...},
    preconditions=[...],
    side_effects=[...],
))

# Publish all schemas
client.schema.publish_all()
print("E-commerce Ontology published successfully!")

#8.2 Business Query Examples

Python
# Query VIP users' recent 30-day orders with shipping status
vip_orders = client.ontology.query(
    object_type="User",
    filter="memberLevel IN ['GOLD', 'PLATINUM', 'DIAMOND']",
    expand=[
        "orders[status='SHIPPED'].shipment",
        "orders.items.product.category",
    ],
    order_by="lifetimeValue DESC",
    limit=100,
)

# Query all low-stock products with their suppliers
low_stock = client.ontology.query(
    object_type="Inventory",
    filter="isLowStock == true",
    expand=[
        "product.brand",
        "product.category",
        "warehouse",
    ],
)

# Cross-type search (using Searchable interface)
search_results = client.ontology.query(
    interface="Searchable",
    filter="searchableText CONTAINS 'wireless bluetooth headphones'",
    order_by="searchRank DESC",
    limit=20,
)

#9. Data Source Mapping

Python
# Map existing MySQL e-commerce database to Ontology
client.connection.register(ConnectionSpec(
    name="ecommerce-mysql",
    type="MYSQL",
    config={"host": "mysql.internal", "port": 3306, "database": "ecommerce"},
    credential_ref="ecommerce-db-cred",
))

# Auto-discover and map
discovery = client.connection.discover_schema("ecommerce-mysql")

# User table mapping
client.connection.create_source_mapping(SourceMappingSpec(
    name="user-mapping",
    connection="ecommerce-mysql",
    source_table="t_user",
    target_object_type="User",
    mode="REPLICATED",
    property_mappings=[
        PropertyMapping("user_id", "userId", primary_key=True),
        PropertyMapping("user_name", "username"),
        PropertyMapping("user_email", "email"),
        PropertyMapping("member_level", "memberLevel",
                       transform="UPPER(value)"),
        PropertyMapping("is_active", "isActive",
                       transform="value = 1"),
    ],
    sync_policy=SyncPolicy(
        schedule="*/5 * * * *",
        watermark_column="updated_at",
    ),
))

# Product table mapping
client.connection.create_source_mapping(SourceMappingSpec(
    name="product-mapping",
    connection="ecommerce-mysql",
    source_table="t_product",
    target_object_type="Product",
    mode="REPLICATED",
    property_mappings=[
        PropertyMapping("product_id", "productId", primary_key=True),
        PropertyMapping("product_name", "name"),
        PropertyMapping("product_sku", "sku"),
        PropertyMapping("base_price", "basePrice"),
        PropertyMapping("current_price", "currentPrice"),
        PropertyMapping("product_status", "status"),
    ],
    sync_policy=SyncPolicy(
        schedule="*/2 * * * *",
        watermark_column="updated_at",
    ),
))

#10. Common Modeling Decisions and Trade-offs

#10.1 Order Snapshot vs Reference

Code
Option A: OrderItem directly references Product (live)
Pros: Data is always current
Cons: Product modifications affect historical order pricing/names

Option B: OrderItem stores ProductSnapshot (snapshot)  Recommended
Pros: Historical order data unaffected by product changes
Cons: Requires additional storage

This design uses Option B: ProductSnapshot StructType

#10.2 Inventory Granularity

Code
Option A: Put stock property directly on Product
Suitable for: Single warehouse scenarios

Option B: Independent Inventory ObjectType  Recommended
Suitable for: Multi-warehouse scenarios
Pros: Each warehouse's inventory managed independently, supports transfers

This design uses Option B: Product --[StoredIn]--> Inventory --[LocatedIn]--> Warehouse

#10.3 User Address Management

Code
Option A: User has addresses: ARRAY[STRING] (JSON array)
Cons: No type checking, hard to query

Option B: Independent Address ObjectType
Cons: Over-modeling, addresses lack independent lifecycle

Option C: User.shippingAddresses: ARRAY[Address] (StructType array)  Recommended
Pros: Has type checking, not over-modeled

#Key Takeaways

  1. The core of an e-commerce Ontology is 5 ObjectTypes + 10 RelationTypes. User, Product, Order, Inventory, and Shipment form the complete business loop, woven together through relations into a traversable semantic graph.

  2. Derived properties make metric computation "declarative." User LTV, product sales volume, inventory turnover rate — all declared with derived: true, automatically computed and cached by the platform without writing ETL.

  3. ActionType is the standardized container for business operations. PlaceOrder, Pay, Ship, and Refund — four core operations declare preconditions, side effects, idempotency strategy, and permission model through ActionType, dramatically reducing boilerplate code.

  4. Modeling decisions should consider long-term evolution. Snapshot vs reference, independent ObjectType vs embedded property, single vs multi-warehouse — each decision should be based on "business needs over the next 12 months."

#Next Article

S4-16: Ontology Practice: Manufacturing Modeling — We will demonstrate Ontology modeling in manufacturing scenarios using Equipment/WorkOrder/Line/QC/Supplier.

tags: ontology, ecommerce, modeling, product, order, inventory, logistics, action-type, derived-property, coomia-dip