哪里可以寻找 data modeling 的设计灵感?
结论
很难找到一个网站,既按业务领域组织内容,又提供 logical data model、解释设计理由,还能免费在线浏览。这类资料通常出现在专业书籍、行业标准或大型产品的业务对象文档中,很少以数据库模型网站的形式发布。
设计 sales module 时,可以结合三类资料:
- 用通用数据模型书籍整理业务实体 checklist。
- 用 UBL、GS1 等行业标准核对订单、价格、税费、履约和发票的语义。
- 参考成熟 ERP/CRM 平台公开的对象模型,了解业务边界,但不要照搬其物理表结构。
Rails 或具体数据库决定的是实现方式,不应成为 logical model 的出发点。
值得参考的资源
《The Data Model Resource Book》
Len Silverston 的《The Data Model Resource Book》系列最接近上述需求。它围绕 Party、Product、Order、Shipment、Invoice、Payment 等常见业务主题给出可复用模型,也会解释某些概念为什么需要拆开。
这套书没有完整的免费在线版本,更适合用来整理 checklist,不适合作为可以直接复制的 schema。
OASIS UBL
Universal Business Language 是一套电子商务单据标准,定义了 Order、Order Response、Despatch Advice、Receipt Advice、Invoice、Credit Note 等业务文档及其组成部分。
应用不必直接保存 UBL 的 XML。它更适合用来检查业务模型是否遗漏了以下内容:
- 买方、卖方、收货方、付款方等不同角色
- 订单行、费用、折扣、税费和价格组成
- 部分发货、部分开票及退货
- 单据引用与版本关系
- 地址、币种、计量单位和税务分类
- Credit Note 等冲销单据
UBL 更像一套规范,并不是完整的建模教程。使用时仍需把文档结构转换成自己的领域模型。
GS1 标准
GS1 的业务标准可用于检查 Product、Trade Item、Location、Party、Order、Delivery 等概念的身份标识和交换语义。
如果系统以后可能与供应商、仓库、物流商或 ERP 对接,GS1 很有参考价值。它可以帮助设计者区分:
- 内部商品 ID 与 GTIN 等外部标识
- 企业、门店和仓库等不同地点
- 商品定义与实际库存批次
- 销售单位、包装单位和运输单位
GS1 面向跨企业数据交换,其中部分复杂设计对单一应用可能并不需要。
schema.org
schema.org 中的 Product、Offer、Order、OrderItem、Invoice、Organization 和 PostalAddress 提供了一套较轻量的概念词汇。
它适合辅助命名和检查基础实体,但不能充当后台销售系统的完整模型。schema.org 主要服务于网页结构化数据,不会深入处理库存分配、订单修订和会计过账等问题。
Microsoft Common Data Model
Microsoft Common Data Model 提供了按业务领域组织的标准实体定义。销售、客户、产品和供应链等领域的实体,可用于观察属性如何划分,以及实体边界如何确定。
这类模型通常包含平台自身的集成需求,适合拿来比较,不宜直接映射成 Rails models。
Salesforce、Dynamics 365 和其他 ERP/CRM 对象文档
成熟平台通常会公开 Product、Price Book、Quote、Order、Order Product、Invoice、Account、Contact 等业务对象的说明。
这些资料虽然不是专门讲解 logical data model 的教程,但通常比 reverse-engineering 数据库更实用。对象文档说明的是业务含义和关系,而不是平台内部的表结构。参考时需要排除由平台权限、同步机制、扩展字段和历史兼容性引入的复杂设计。
如何把这些资料变成 checklist
先根据业务生命周期建模,不要一开始就列数据库表:
Catalog
Product
ProductVariant
ProductIdentifier
UnitOfMeasure
Commercial terms
PriceList
Price
Discount
TaxCategory
Currency
Customer
Party
PartyRole
ContactPoint
Address
Sales
Quote
SalesOrder
SalesOrderLine
OrderAdjustment
Fulfillment
Fulfillment
Shipment
ShipmentLine
InventoryAllocation
Return
Billing
Invoice
InvoiceLine
CreditNote
PaymentAllocation
重点不在实体叫什么,而在于是否正确区分了不同事实。比如,客户当前地址与提交订单时的收货地址是两个事实;商品当前价格与订单成交价格也是两个事实。
然后逐项确认:
- 一个订单是否允许分成多个发货批次?
- 一次发货是否可能只完成某条订单行的一部分?
- 一个订单是否允许分多次开票?
- 一张发票是否可能覆盖多个订单或发货单?
- 订单确认后,商品名称、税率和价格是否需要保持不变?
- 折扣属于订单级、订单行级,还是两者都支持?
- 取消、退款、退货和 Credit Note 是否被误当成了同一件事?
- 客户只需要一个简单的
Customer,还是应该由Party加买方、收货方等角色组成? - 是否需要保存报价单转订单、订单转发货单、发货单转发票的来源关系?
- 币种、税额和金额采用什么精度与舍入规则?
- 是否需要审计状态变化?
Rails 实现示例
确定 logical model 后,再将它映射为 Rails models。下面只演示订单快照和部分履约关系,不代表完整的数据库设计:
class SalesOrder < ApplicationRecord
belongs_to :customer
has_many :sales_order_lines, dependent: :restrict_with_exception
has_many :fulfillments, dependent: :restrict_with_exception
has_many :invoices, dependent: :restrict_with_exception
enum :status, {
draft: "draft",
confirmed: "confirmed",
partially_fulfilled: "partially_fulfilled",
fulfilled: "fulfilled",
cancelled: "cancelled"
}
validates :currency_code, presence: true
end
class SalesOrderLine < ApplicationRecord
belongs_to :sales_order
belongs_to :product_variant
has_many :fulfillment_lines, dependent: :restrict_with_exception
has_many :invoice_lines, dependent: :restrict_with_exception
validates :quantity, numericality: { greater_than: 0 }
validates :unit_price, numericality: true
end
class Fulfillment < ApplicationRecord
belongs_to :sales_order
has_many :fulfillment_lines, dependent: :restrict_with_exception
end
class FulfillmentLine < ApplicationRecord
belongs_to :fulfillment
belongs_to :sales_order_line
validates :quantity, numericality: { greater_than: 0 }
end
订单行通常还要保存成交时的快照,不能只通过 product_variant 读取当前数据:
class AddSnapshotsToSalesOrderLines < ActiveRecord::Migration[8.0]
def change
add_column :sales_order_lines, :product_name, :string, null: false
add_column :sales_order_lines, :sku, :string, null: false
add_column :sales_order_lines, :unit_price, :decimal,
precision: 19, scale: 4, null: false
add_column :sales_order_lines, :tax_rate, :decimal,
precision: 9, scale: 6
add_column :sales_order_lines, :currency_code, :string,
limit: 3, null: false
end
end
ActiveRecord::Migration[8.0] 仅用于示例,需要替换成项目实际使用的 Rails migration 版本。
几个容易踩坑的地方
不要把 Order、Shipment 和 Invoice 合并成一张“大单据表”。在简单流程中,它们看起来是一一对应的。一旦需要部分发货、分批开票、退货或补开发票,彼此之间很快就会变成多对多关系,或需要通过明细行建立关联。
历史交易也不能完全依赖可变的主数据。商品名称、地址、价格、税率和客户抬头都可能发生变化,因此已确认单据通常需要保存交易发生时的快照。
过早建立高度抽象的通用模型同样有风险。例如,把所有对象都设计成 Party、Resource、Event 和动态属性,理论上很灵活,实际可能增加查询、约束和团队理解的难度。只有在确认存在多个角色或类型后,才有必要引入这类抽象。
与其寻找一个可以直接套用的网站,不如用《The Data Model Resource Book》搭建骨架,通过 UBL 和 GS1 检查业务语义,再参考成熟 ERP/CRM 的对象文档验证边界。最终模型仍应由实际销售流程、履约方式、税务要求和审计要求决定。
备注:内容仅供参考。
