返回博客

Ontology 实战:电商平台建模

一个中型电商平台通常涉及以下核心业务实体:

Coomia发布于 2025年8月19日16 分钟阅读
分享本文Twitter / X

Ontology 实战:电商平台建模

系列:S4 本体建模 · 第 15 篇 | 难度:中级 | 阅读时间:18 分钟

#TL;DR

  • 电商平台是 Ontology 建模的经典练习场——Product、Order、User、Inventory、Logistics 五大核心 ObjectType 覆盖了从商品上架到用户收货的完整业务链路,展示了 Ontology 五元组的全部建模能力。
  • 派生属性消除了"指标散落在代码中"的痛点——商品销量、用户 LTV、库存周转率等关键指标通过声明式定义自动计算,无需写额外的 ETL 或统计 SQL。
  • ActionType 将业务操作标准化——下单、支付、发货、退款四大核心操作通过 ActionType 声明,平台自动处理幂等、权限、审计,开发者只需关注业务逻辑。

#1. 引言:电商系统的建模挑战

一个中型电商平台通常涉及以下核心业务实体:

Code
┌─────────────────────────────────────────────────────────┐
│                    电商平台业务全景                        │
│                                                         │
│  ┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐          │
│  │ 用户  │───►│ 订单  │───►│ 物流  │───►│ 签收  │          │
│  └──┬───┘    └──┬───┘    └──────┘    └──────┘          │
│     │           │                                      │
│     │      ┌────┴────┐                                 │
│     │      │ 订单项   │                                 │
│     │      └────┬────┘                                 │
│     │           │                                      │
│     │      ┌────┴────┐    ┌──────┐    ┌──────┐         │
│     └─────►│ 商品     │◄───│ 库存  │◄───│ 仓库  │         │
│            └────┬────┘    └──────┘    └──────┘         │
│                 │                                      │
│            ┌────┴────┐    ┌──────┐                     │
│            │ 品类     │    │ 品牌  │                     │
│            └─────────┘    └──────┘                     │
└─────────────────────────────────────────────────────────┘

传统 ER 建模只能描述这些实体的数据结构,而 Ontology 建模还需要回答:

  • 用户的终身价值(LTV)如何实时计算?
  • "下单"这个操作需要检查哪些前置条件?
  • 库存不足时如何自动触发采购?
  • 如何跨类型统一查询所有"可搜索"的对象?

#2. ObjectType 定义

#2.1 用户(User)

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: User
  displayName: 用户
spec:
  primaryKey: userId
  implements:
    - Auditable
    - Taggable
  properties:
    userId:
      type: STRING
      required: true
      description: 用户唯一标识
    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

    # 派生属性
    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: 商品
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: CNY
    weight:
      type: DOUBLE
      description: 重量(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

    # 派生属性
    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: 订单
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]
    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

    # 派生属性
    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: 订单项
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: 下单时的商品快照,防止商品修改后影响历史订单

#2.5 库存(Inventory)

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Inventory
  displayName: 库存
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: 已被订单锁定但未出库的数量
    damagedQuantity:
      type: INTEGER
      default: 0
    reorderPoint:
      type: INTEGER
      default: 10
      description: 安全库存阈值
    maxQuantity:
      type: INTEGER
      description: 最大库存量
    isLowStock:
      type: BOOLEAN
      derived: true
      expression: "availableQuantity <= reorderPoint"
    turnoverRate:
      type: DOUBLE
      derived: true
      expression: "soldLast30Days / AVG(availableQuantity)"
      description: 库存周转率
    daysOfSupply:
      type: DOUBLE
      derived: true
      expression: "availableQuantity / (soldLast30Days / 30)"
      description: 按当前销售速度可供应的天数

#2.6 物流(Shipment)

YAML
apiVersion: ontology/v1
kind: ObjectType
metadata:
  name: Shipment
  displayName: 物流
spec:
  primaryKey: shipmentId
  implements:
    - Auditable
    - Statusable
    - Locatable
  properties:
    shipmentId:
      type: STRING
      required: true
    trackingNumber:
      type: STRING
      constraints:
        unique: true
    carrier:
      type: STRING
      enum: [SF_EXPRESS, ZTO, YTO, STO, YUNDA, JD_LOGISTICS, EMS]
    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 定义

YAML
# 地址结构
kind: StructType
metadata:
  name: Address
spec:
  properties:
    province: { type: STRING }
    city: { type: STRING }
    district: { type: STRING }
    street: { type: STRING }
    postalCode: { type: STRING }
    receiverName: { type: STRING }
    receiverPhone: { type: STRING }

---
# 商品尺寸
kind: StructType
metadata:
  name: Dimensions
spec:
  properties:
    length: { type: DOUBLE, description: "长度(cm)" }
    width: { type: DOUBLE, description: "宽度(cm)" }
    height: { type: DOUBLE, description: "高度(cm)" }

---
# 商品快照(订单项中冻结的商品信息)
kind: StructType
metadata:
  name: ProductSnapshot
spec:
  properties:
    productId: { type: STRING }
    name: { type: STRING }
    sku: { type: STRING }
    imageUrl: { type: STRING }
    price: { type: DOUBLE }

---
# 物流追踪事件
kind: StructType
metadata:
  name: TrackingEvent
spec:
  properties:
    timestamp: { type: TIMESTAMP }
    location: { type: STRING }
    description: { type: STRING }
    status: { type: STRING }

#4. InterfaceType 定义

YAML
# 可搜索接口
kind: InterfaceType
metadata:
  name: Searchable
spec:
  properties:
    searchableText:
      type: STRING
      description: 全文搜索索引字段
    searchRank:
      type: DOUBLE
      description: 搜索排序权重
  implementedBy:
    - Product
    - Category
    - Brand

---
# 可评价接口
kind: InterfaceType
metadata:
  name: Reviewable
spec:
  properties:
    averageRating:
      type: DOUBLE
    reviewCount:
      type: INTEGER
    latestReviewAt:
      type: TIMESTAMP
  implementedBy:
    - Product
    - Shipment

#5. RelationType 定义

Python
from ontology_sdk import RelationType

# 核心关系定义
relations = [
    # 用户 → 订单
    RelationType(
        name="PlacedOrder",
        source="User",
        target="Order",
        cardinality="ONE_TO_MANY",
        description="用户下单",
    ),

    # 订单 → 订单项
    RelationType(
        name="ContainsItem",
        source="Order",
        target="OrderItem",
        cardinality="ONE_TO_MANY",
        description="订单包含的商品项",
        cascade_delete=True,
    ),

    # 订单项 → 商品
    RelationType(
        name="RefersToProduct",
        source="OrderItem",
        target="Product",
        cardinality="MANY_TO_ONE",
        description="订单项对应的商品",
    ),

    # 商品 → 品类
    RelationType(
        name="BelongsToCategory",
        source="Product",
        target="Category",
        cardinality="MANY_TO_ONE",
        description="商品所属品类",
    ),

    # 商品 → 品牌
    RelationType(
        name="ProducedByBrand",
        source="Product",
        target="Brand",
        cardinality="MANY_TO_ONE",
        description="商品所属品牌",
    ),

    # 商品 → 库存(通过仓库)
    RelationType(
        name="StoredIn",
        source="Product",
        target="Inventory",
        cardinality="ONE_TO_MANY",
        properties={
            "warehouseId": "STRING",
        },
        description="商品在各仓库的库存",
    ),

    # 库存 → 仓库
    RelationType(
        name="LocatedInWarehouse",
        source="Inventory",
        target="Warehouse",
        cardinality="MANY_TO_ONE",
        description="库存所在仓库",
    ),

    # 订单 → 物流
    RelationType(
        name="ShippedVia",
        source="Order",
        target="Shipment",
        cardinality="ONE_TO_ONE",
        description="订单的物流信息",
    ),

    # 用户 → 商品(浏览)
    RelationType(
        name="Viewed",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "viewedAt": "TIMESTAMP",
            "duration": "INTEGER",
        },
        description="用户浏览商品记录",
    ),

    # 用户 → 商品(收藏)
    RelationType(
        name="Favorited",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "favoritedAt": "TIMESTAMP",
        },
        description="用户收藏商品",
    ),

    # 用户 → 商品(评价)
    RelationType(
        name="ReviewedProduct",
        source="User",
        target="Product",
        cardinality="MANY_TO_MANY",
        properties={
            "rating": "INTEGER",
            "comment": "STRING",
            "reviewedAt": "TIMESTAMP",
            "images": "ARRAY[STRING]",
        },
        description="用户对商品的评价",
    ),
]

关系图:

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

    Order ──ShippedVia──► Shipment

#6. ActionType 定义

#6.1 下单(PlaceOrder)

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: PlaceOrder
  displayName: 下单
spec:
  description: 用户提交订单
  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: 用户已被禁用
    - name: items_in_stock
      expression: "ALL(items, item => Product[item.productId].availableStock >= item.quantity)"
      errorMessage: 部分商品库存不足
    - name: items_on_sale
      expression: "ALL(items, item => Product[item.productId].status == 'ACTIVE')"
      errorMessage: 部分商品已下架
  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 支付(PayOrder)

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: PayOrder
  displayName: 支付订单
spec:
  objectType: Order
  executor: FUNCTION
  parameters:
    orderId:
      type: STRING
      required: true
    paymentMethod:
      type: STRING
      required: true
      enum: [ALIPAY, WECHAT_PAY, CREDIT_CARD, BANK_TRANSFER]
    paymentToken:
      type: STRING
      required: true
  preconditions:
    - name: order_pending
      expression: "Order[orderId].status == 'PENDING'"
      errorMessage: 订单状态不允许支付
    - name: not_expired
      expression: "DATEDIFF(NOW(), Order[orderId].createdAt) < 30"
      errorMessage: 订单已超时,请重新下单
  sideEffects:
    - type: UPDATE
      target: Order
      properties:
        status: PAID
        paymentMethod: parameters.paymentMethod
        paidAt: NOW()
  timeout: 30s
  retryPolicy:
    maxRetries: 3
    backoff: EXPONENTIAL

#6.3 发货(ShipOrder)

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: ShipOrder
  displayName: 发货
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 退款(RefundOrder)

YAML
apiVersion: ontology/v1
kind: ActionType
metadata:
  name: RefundOrder
  displayName: 退款
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: 退款证据图片URL
  preconditions:
    - name: refundable_status
      expression: "Order[orderId].status IN ['PAID', 'SHIPPED', 'DELIVERED']"
    - name: within_refund_window
      expression: "DATEDIFF(NOW(), Order[orderId].completedAt) <= 7"
      errorMessage: 已超过 7 天退款期限
  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. 指标设计

Python
# 商品指标
product_metrics = [
    MetricSpec(
        name="ProductConversionRate",
        description="商品浏览到购买的转化率",
        expression="totalSold / viewCount",
        dimensions=["category", "brand"],
        unit="percentage",
    ),
    MetricSpec(
        name="ProductGMV",
        description="商品成交总额",
        object_type="Product",
        property="totalRevenue",
        aggregation="SUM",
        dimensions=["category", "brand"],
        time_series=True,
        unit="CNY",
    ),
]

# 订单指标
order_metrics = [
    MetricSpec(
        name="OrderAverageValue",
        description="客单价",
        object_type="Order",
        property="payableAmount",
        aggregation="AVG",
        filter="status NOT IN ['CANCELLED', 'REFUNDED']",
        dimensions=["paymentMethod"],
        time_series=True,
        unit="CNY",
    ),
    MetricSpec(
        name="OrderFulfillmentRate",
        description="订单履约率",
        expression="COUNT(status='COMPLETED') / COUNT(status='PAID')",
        dimensions=["warehouse"],
        unit="percentage",
    ),
    MetricSpec(
        name="AverageDeliveryDays",
        description="平均配送天数",
        object_type="Order",
        property="deliveryDays",
        aggregation="AVG",
        filter="status = 'COMPLETED'",
        dimensions=["carrier", "region"],
        unit="days",
    ),
]

# 用户指标
user_metrics = [
    MetricSpec(
        name="UserRetentionRate",
        description="用户留存率(30天)",
        expression="COUNT(hasOrderInLast30Days) / COUNT(ALL)",
        dimensions=["memberLevel", "registrationChannel"],
        unit="percentage",
    ),
    MetricSpec(
        name="UserLifetimeValue",
        description="用户终身价值",
        object_type="User",
        property="lifetimeValue",
        aggregation="AVG",
        dimensions=["memberLevel"],
        unit="CNY",
    ),
]

# 库存指标
inventory_metrics = [
    MetricSpec(
        name="InventoryTurnoverRate",
        description="库存周转率",
        object_type="Inventory",
        property="turnoverRate",
        aggregation="AVG",
        dimensions=["warehouse", "category"],
        alert_rules=[
            AlertRule("turnover-low", "value < 2", "WARNING",
                     "仓库 {warehouse} 品类 {category} 周转率低于 2"),
        ],
    ),
    MetricSpec(
        name="LowStockItems",
        description="低库存商品数",
        object_type="Inventory",
        filter="isLowStock == true",
        aggregation="COUNT",
        dimensions=["warehouse"],
        alert_rules=[
            AlertRule("low-stock-critical", "value > 50", "CRITICAL",
                     "仓库 {warehouse} 有 {value} 个商品低库存"),
        ],
    ),
]

#8. 完整代码示例

#8.1 使用 SDK 创建电商 Ontology

Python
from ontology_sdk import OntologyClient, ObjectTypeSpec, PropertySpec

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

# 创建 StructType
client.schema.create_struct_type(StructTypeSpec(
    name="Address",
    properties={
        "province": PropertySpec(type="STRING"),
        "city": PropertySpec(type="STRING"),
        "district": PropertySpec(type="STRING"),
        "street": PropertySpec(type="STRING"),
        "postalCode": PropertySpec(type="STRING"),
        "receiverName": PropertySpec(type="STRING"),
        "receiverPhone": PropertySpec(type="STRING"),
    },
))

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

# 创建 ObjectType: User
client.schema.create_object_type(ObjectTypeSpec(
    name="User",
    display_name="用户",
    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)",
        ),
    },
))

# 创建其他 ObjectType ...
# (Product, Order, OrderItem, Inventory, Shipment)

# 创建 RelationType
client.schema.create_relation_type(RelationTypeSpec(
    name="PlacedOrder",
    source="User",
    target="Order",
    cardinality="ONE_TO_MANY",
))

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

# 发布所有 Schema
client.schema.publish_all()
print("E-commerce Ontology published successfully!")

#8.2 业务查询示例

Python
# 查询 VIP 用户最近 30 天的订单及物流状态
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,
)

# 查询所有低库存商品及其供应商
low_stock = client.ontology.query(
    object_type="Inventory",
    filter="isLowStock == true",
    expand=[
        "product.brand",
        "product.category",
        "warehouse",
    ],
)

# 跨类型搜索(使用 Searchable 接口)
search_results = client.ontology.query(
    interface="Searchable",
    filter="searchableText CONTAINS '无线蓝牙耳机'",
    order_by="searchRank DESC",
    limit=20,
)

#9. 数据源映射

Python
# 将现有 MySQL 电商数据库映射到 Ontology
client.connection.register(ConnectionSpec(
    name="ecommerce-mysql",
    type="MYSQL",
    config={"host": "mysql.internal", "port": 3306, "database": "ecommerce"},
    credential_ref="ecommerce-db-cred",
))

# 自动发现并映射
discovery = client.connection.discover_schema("ecommerce-mysql")

# 用户表映射
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",
    ),
))

# 商品表映射
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. 常见建模决策与权衡

#10.1 订单快照 vs 引用

Code
方案 A:OrderItem 直接引用 Product(实时)
优点:数据始终最新
缺点:商品修改后历史订单的价格/名称会变

方案 B:OrderItem 保存 ProductSnapshot(快照)  ✅ 推荐
优点:历史订单数据不受商品修改影响
缺点:需要额外存储

本方案采用 B:使用 ProductSnapshot StructType

#10.2 库存粒度

Code
方案 A:Product 上直接放 stock 属性
适用:单仓库场景

方案 B:独立 Inventory ObjectType  ✅ 推荐
适用:多仓库场景
优点:每个仓库的库存独立管理,支持调拨

本方案采用 B:Product --[StoredIn]--> Inventory --[LocatedIn]--> Warehouse

#10.3 用户地址管理

Code
方案 A:User 上放 addresses: ARRAY[STRING](JSON 数组)
缺点:无类型检查、难以查询

方案 B:独立 Address ObjectType
缺点:过度建模,地址没有独立生命周期

方案 C:User.shippingAddresses: ARRAY[Address](StructType 数组)  ✅ 推荐
优点:有类型检查,不过度建模

#Key Takeaways

  1. 电商 Ontology 的核心是 5 个 ObjectType + 10 个 RelationType。User、Product、Order、Inventory、Shipment 构成了完整的业务闭环,通过关系将它们编织成一个可遍历的语义图。

  2. 派生属性让指标计算"声明式化"。用户 LTV、商品销量、库存周转率等都通过 derived: true 声明,平台自动计算和缓存,无需写 ETL。

  3. ActionType 是业务操作的标准化容器。下单、支付、发货、退款四个核心操作通过 ActionType 声明前置条件、副作用、幂等策略和权限模型,大幅减少重复代码。

  4. 建模决策要考虑长期演进。快照 vs 引用、独立 ObjectType vs 嵌入属性、单仓 vs 多仓——每个决策都应该基于"未来 12 个月的业务需求"来判断。

#下一篇

S4-16: Ontology 实战:制造业建模 —— 我们将用 Equipment/WorkOrder/Line/QC/Supplier 展示制造业场景下的 Ontology 建模实践。

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