Ontology Practice: E-Commerce Modeling
A mid-size e-commerce platform typically involves these core business entities:
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:
┌─────────────────────────────────────────────────────────┐
│ 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
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
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
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
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
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
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
# 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
# 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
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:
┌──────────┐
│ 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
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
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
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
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
# 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
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
# 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
# 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
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
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
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
-
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.
-
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. -
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.
-
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