软件分析与设计笔记 09:功能需求(二):从流程到业务实体

活动图画完之后,怎么从流程里筛出真正的业务实体:三条判断标准、哪些名词其实不该建实体、属性从哪里来,以及领域模型和后面的数据模型分别负责什么。

学习#需求分析#领域建模#软件设计#学习笔记

本文是 《软件分析与设计笔记》 系列的第 9 篇,共 28 篇

这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的,示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。

贯穿案例:片区家电维修派单系统。08 篇画完了用例图和活动图,这一篇从流程里抽出业务实体,产出领域模型 —— 它是 18 篇 ERD 的原料。

0 流程画对了,那系统要记住什么

08 篇那张「申请配件」的活动图画完,流程是通的。但开发会接着问:

「工程师提交申请的时候,系统要记住哪些信息?是记住「谁申请了什么」,还是记住「库存扣了多少」?」

这个问题已经不属于流程层了。它问的是:这个业务里有哪些东西需要被系统长期记住——也就是业务实体(business entity)。

1 从流程里找名词

起步方法很朴素:把活动图和用例描述里出现的名词圈出来。但圈出来的东西里,只有一部分是实体,其余要么是动作,要么是界面元素,要么是算出来的值。

拿 08 篇那张活动图圈一遍,会得到这样一堆词:

text
工程师、工单、备料申请、配件、库存、出库单、数量、
提交、查询、校验、待件挂起、安全库存、缺料率

这十几个词里,真正该建实体的只有五个左右。剩下的怎么筛,就是下面三条标准。

从活动图提取业务实体的三条判断标准与领域模型示例:三条标准是必须是业务概念而不是动作、必须需要被系统记住、必须有多个实例;下方是贯穿案例的领域模型类图,包含工单、配件消耗、库存项、工程师、客户五个类及其关系与重数

2 三条筛选标准

2.1 是业务概念,不是动作

「提交备料申请」是动作,「备料申请」才可能是概念。「查询库存」是动作,「库存项」才是概念。

上面那堆词里,提交、查询、校验、待件挂起全是动作或状态,先划掉。

有一个边界情况值得单独说:「备料申请」和「配件消耗」是不是同一个东西?

  • 如果业务上要追踪「申请了几次、每次批了多少、被拒过没有」→ 申请本身是实体;
  • 如果只是「这一次用了两个压缩机」,用完就完了 → 它就是工单上的一条配件消耗记录,不是独立实体。

判断依据是业务上要不要追踪它,而不是「它听起来像不像一个东西」。

2.2 系统需要长期记住它

只在一个界面里瞬间用一下的数据(比如当前选中的那一行、筛选条件「只看空调」),不需要建实体。判断依据是:下一次会话还能用得上吗?

这条标准在本例里能筛掉不少东西:

  • 「当前筛选的片区」—— 不用记住,划掉;
  • 「安全库存阈值」—— 要记住,而且每个配件不同,所以它是 StockItem 上的一个属性,不是独立实体;
  • 「缺料率」—— 不用存,它是算出来的(见 §3)。

2.3 它有多个实例

只有一个的配置类对象(比如「系统设置」),通常不该建成实体,直接作为配置项或常量处理就好。

本例里有个现成的例子:「派单规则」(比如「同一工程师一天不超过 5 单」)。如果整套系统只有一条规则,那它是配置;但如果将来要按片区、按技能等级设不同的规则,那它就该是实体。

第 ③ 条有个常见的例外:即便当前只有一个实例(比如「公司」),如果业务上将来可能变成多个(多分公司),也值得建实体。判断标准是业务会不会扩张,不是现在有几个。

本例恰好就是这个情况:现在只有一个片区在试点,但王姐明确说了「跑通了要推到另外五个片区」。所以片区(District)就该建实体,尽管当前只有一个 —— 否则三个月后要改表结构。

3 哪些东西不该建实体

不该建实体的东西本例中的例子更好的处理方式
动作提交申请、查询库存、派单作为操作(操作记录可能才是实体)
界面元素派单按钮、筛选下拉框属于界面设计,不属于领域模型
计算出来的值缺料率、平均上门时长、完成率由其他字段算出,不存储
只出现一次的配置派单规则(如果全公司只有一条)配置项或枚举
外部系统的内部数据供应商的仓库编号只在必要处保存引用字段

「计算出来的值」这一条最容易出错。把「缺料率」存进实体,就意味着每次工单状态变动都要记得同步更新它 —— 一旦漏了,数据就永久不一致。能算出来的不要存(这也是后面讲规范化时反复出现的主题,见笔记 19)。

这里还有个反直觉的点:缺料率是考核王姐的 KPI,但它依然不该存。KPI 是报表层的事,存的是原始事实(每张工单有没有缺料),算的是展示层。把 KPI 存成字段,等于把「口径」焊死在数据里,以后想换算法就得改表。

4 属性从哪里来

4.1 属性来自「系统需要记住的信息」

实体确定了,属性(attribute)从哪里来?来自用例描述里那些「系统需要记住的信息」。

举一个具体做法:翻开「申请配件」这个用例的描述,每读到一句与数据有关的句子,就把它变成一个属性:

用例描述里的句子变成的属性
「工程师选择工单,输入配件编号与数量」PartUsage.partNo、PartUsage.qty
「系统校验工单状态是否为进行中」WorkOrder.status
「系统根据配件编号查询当前库存」StockItem.onHandQty
「系统调用供应商接口获取可售数量」不建属性 —— 那是外部实时数据,用时去查
「若可满足,系统生成出库单,并扣减库存」IssueNote.issuedAt、StockItem.onHandQty(更新)
「系统把申请记录写入工单」PartUsage.workOrderId

注意第四行:外部系统的实时数据不落成属性。这条很容易被忽略 —— 有人会把「供应商可售量」也存一份,然后就永久不一致了。

4.2 每个属性都要能追溯

这样做的好处是每个属性都能追溯到一句需求。反过来,如果一个属性说不出它来自哪句话,多半是你自己加上去的(通常不是坏事,但要意识到这是你做的决定)。

本例里有个真实的例子:WorkOrder.priority(工单优先级)。用例描述里没提过,但如果你加上它,就得能说出理由 —— 「因为看板要用红黄绿灯排序,而排序需要一个优先级」。这个理由成立,但要写进假设里。

4.3 标识符

其中有些属性会承担特殊角色:能唯一区分同类实例的那个,叫标识符(identifier),例如 WorkOrder.orderNo。它后面会变成数据表里的主键(见笔记 18)。

本例里有个值得注意的细节:工单号该用什么?

  • 自增数字(1001、1002)—— 简单,但会暴露业务量;
  • 带日期前缀(WO-20261119-001)—— 客服在电话里好念,客户也认;
  • UUID —— 不会冲突,但没法口头念。

这个选择看起来是技术细节,其实受业务影响(客服要在电话里报工单号)。这就是为什么标识符要在领域模型里就定下来。

5 领域模型(Domain Model)长什么样

5.1 三样东西

把这些实体和它们的关系画出来,就是领域模型,通常用类图(class diagram)表示:

  • 实体类(entity class) —— 对应一个业务实体,用业务术语命名(WorkOrder、Engineer),不要用技术名词(WorkOrderTable、WorkOrderDTO);
  • 属性 —— 业务关心的那些信息;
  • 关系(relationship)与重数(multiplicity) —— 一个客户可以有多张工单(1 对 *);一张工单可以有多条配件消耗(1 对 *);一条配件消耗只对应一个库存项(* 对 1)。

把这三样落到图上,就是本例的领域模型:

派单宝的领域模型类图:五个实体类排成两行,第一行是客户、工单、配件消耗,第二行是工程师与库存项。每个类分三格,自上而下是类名、带类型的属性、操作。关系方面,客户与工单为一对多并标注报修,工单与配件消耗为一对多组合且实心菱形贴在工单这一端并标注包含,工单与工程师为多对一关联并标注派给,配件消耗与库存项为多对一关联并标注引用。底部图例说明四条线各自的意思、重数怎么读,以及这一阶段刻意不写的东西

5.2 怎么读这张图

这张图里有四个地方值得逐一看,因为它们每一个都对应一条业务判断:

看什么图上长什么样背后的业务判断
类名WorkOrder 而不是 WORK_ORDER / WorkOrderDTO概念模型用业务术语,表名属于数据模型(§6)
属性- status : WorkOrderStatus属性来自「系统需要记住的信息」(§4.1),且只对实体类定义
操作+ canRequestParts()行为跟着数据走 —— 判断「什么状态允许申请」属于工单自己(笔记 20)
关系实心菱形 vs 普通实线组合与关联的差别(见下)

关系的两种画法在这一张图上就能对照出来,这是本例最值得讲的一处:

  • 工单 ◆—— 配件消耗 是组合(实心菱形):配件消耗行离开工单没有意义,工单作废它跟着失效;
  • 配件消耗 —— 库存项 只是关联(图上线名写「引用」):库存项独立存在,配件消耗只是记了个 partNo。

这两个如果都画成组合,实现的人会以为「删工单要删库存项」 —— 那是数据事故,因为库存项是独立资产。所以图上线名不是装饰,它承担了区分语义的作用。

还有一个细节:PartUsage.amount() 是操作,不是属性。因为金额能由单价 × 数量算出来 —— 能算出来的不要存(§4、笔记 19)。

5.3 重数要问业务方

重数是需要认真确认的地方,它直接决定后面数据表怎么建。「一张工单能有多条配件消耗吗」这种问题,只有业务方能回答,不要自己假设。

本例里有三个重数特别值得问清楚,因为它们都会改变表结构:

要确认的问题如果答「是」如果答「否」
一张工单能有多条配件消耗吗?需要独立的 PartUsage 表可以直接放在工单上
一个客户能有多台机器吗?需要 Appliance 实体机器信息直接写在工单上
一个工程师能同时属于多个片区吗?需要多对多关联表Engineer 上一个外键就够

第三个问题在本例里答案很可能是「能」—— 旺季时跨片区支援。这一个「能」字,就决定了要不要多一张关联表。

6 领域模型和数据模型不是一回事

这是最容易混淆的一点。领域模型(domain model)和数据模型(后面笔记 18–19 要讲的 ERD)关注的东西不同:

领域模型数据模型
关注业务概念与它们的关系数据怎么存、表怎么建
允许继承、多态这类概念性表达需要落到具体的表与字段
命名业务术语常常带类型后缀、缩写
变化频率业务变它就变随技术选型调整

领域模型是一种概念模型(conceptual model):它只描述业务概念,不涉及实现细节(implementation detail)。一个业务概念在数据库里可能拆成三张表,也可能被合并进别的表 —— 那是设计阶段的事。领域模型先把业务说清楚,暂不背实现的包袱(这一点在笔记 20–23 还会反复出现)。

本例里有个具体例子能说明差别:领域模型里 WorkOrder 和 PartUsage 是「整体—部分」关系,并且 PartUsage 随工单一起消亡。到了数据模型阶段,这条关系要落成「PartUsage 表带 workOrderId 外键,且 ON DELETE CASCADE」。领域模型说的是「概念上它依附于工单」,数据模型才决定「用外键还是别的机制实现」。

本节自测

  1. 「提交备料申请」和「备料申请」,哪个是实体?判断依据是什么?
  2. 为什么「缺料率」不该作为属性存储?它同时是 KPI,这有影响吗?
  3. 「目前只有一个片区」是否意味着不需要建片区实体?为什么?
  4. 领域模型的属性从哪里推导出来?举出本例中一个「不建属性」的情况。
  5. 领域模型和数据模型在「命名」上的差别是什么?为什么会有这个差别?
  6. 本例中「一个工程师能同时属于多个片区吗」这个问题,为什么值得单独问?
  7. 领域模型里,「工单与配件消耗」和「配件消耗与库存项」这两条线在本例中应该怎么画?画错的后果是什么?

参考答案

  1. 「备料申请」可能是实体,「提交」是动作。判断依据是业务上要不要追踪它 —— 如果要记「申请了几次、批了多少、被拒过没有」,它就是实体;如果只是「用了两个压缩机」这种一次性记录,那它就是工单上的一条配件消耗(§2.1)。
  2. 因为它是算出来的值,存进去就意味着每次状态变动都要同步更新,漏一次就永久不一致。它同时是 KPI 也不改变这个结论 —— KPI 是报表层的事,存原始事实、算展示值;把 KPI 存成字段等于把口径焊死在数据里(§3)。
  3. 不意味着。判断标准是业务会不会扩张,不是现在有几个。王姐明确说要推到另外五个片区,所以现在就该建实体,否则三个月后要改表(§2.3)。
  4. 属性来自用例描述里「系统需要记住的信息」。本例中「供应商可售数量」不建属性 —— 那是外部实时数据,用时去查,存下来就永久不一致(§4.1)。
  5. 领域模型用业务术语(WorkOrder、PartUsage),数据模型常带类型后缀或缩写(t_work_order、wo_id)。差别来自关注点不同:领域模型给业务方看,数据模型给实现看(§6)。
  6. 因为它的答案直接决定表结构:答「能」就要多一张多对多关联表,答「不能」就只需 Engineer 上一个外键。本例旺季会跨片区支援,所以答案很可能是「能」(§5.3)。
  7. 工单 ◆—— 配件消耗画成组合(实心菱形贴工单端,配件消耗离开工单没有意义);配件消耗 —— 库存项画成普通关联(库存项独立存在,只是被引用)。如果把后者也画成组合,实现的人会以为删工单要删库存项 —— 那是数据事故(§5.2)。

术语表

术语中文一句话解释
business entity业务实体业务中需要被系统长期记住的对象
domain model领域模型业务概念及它们之间关系的模型
entity class实体类领域模型里代表一个业务实体的类
attribute属性实体上需要记录的信息项
relationship关系两个实体之间的业务关联
multiplicity重数关系两端各自可以有多少个实例
class diagram类图用类、属性与关系表达静态结构的图
identifier标识符能唯一区分同一实体各实例的信息
conceptual model概念模型只描述业务概念、不涉及实现细节的模型
implementation detail实现细节与具体技术选型相关的表达方式,概念模型里应避免

© 2026 HuanYu Blog

记录技术,也记录思考