数据流图(DFD)展示信息如何在系统中流动:它从哪里来,被什么转换,存放在哪里,又流向哪里。它不涉及时序和循环,也不说明哪台服务器运行什么,而这恰恰是它的优势。DFD 是就“系统处理哪些数据”达成共识的最快方式,所以它常出现在需求分析、隐私评审和威胁建模中。
本文先介绍两种经典记法下的 DFD 符号,然后用一个例子(一家网店)走完三个层次:上下文图、0 层图和 1 层图。文中还会解释围绕“0 层”的命名混乱、让各层保持一致的平衡规则,以及评审者会重点检查的错误。
DFD 符号
DFD 使用四种元素。主流记法有两种,都来自 20 世纪 70 年代末的结构化分析书籍:Yourdon 与 DeMarco 记法,以及 Gane 与 Sarson 记法。
| 元素 | 含义 | Yourdon 与 DeMarco | Gane 与 Sarson |
|---|---|---|---|
| 外部实体 | 位于边界之外、发送或接收数据的人、组织或系统 | 矩形 | 正方形,常带阴影 |
| 处理 | 转换数据的工作 | 圆形(气泡) | 带编号栏的圆角矩形 |
| 数据存储 | 静止的数据:表、文件或队列 | 两条平行线 | 一侧开口的矩形,带有 D1 这样的编号 |
| 数据流 | 流动中的数据,用名词命名 | 箭头 | 箭头 |
两种记法是等价的,选一种并坚持使用。许多现代工具把数据存储画成圆柱体,读者一眼就能认出。无论选哪种,都要加上图例。
让 DFD 合法的规则
- 每个处理至少有一个输入和一个输出。 只有输入的处理是黑洞,只有输出的处理是奇迹。
- 数据只能经由处理流动。 不允许有数据流直接从外部实体流向数据存储、从一个数据存储流向另一个,或在两个外部实体之间流动。
- 数据流的名称要描述数据,例如“订单详情”或“支付结果”,而不是“发送”或“检查”这样的动作。
- 处理以动词加宾语命名:“接收订单”“履行订单”。
- 每个处理都要编号,分解后的子处理沿用该编号:处理 1 分解为 1.1、1.2 和 1.3。
层次,以及 0 层的混乱
DFD 自顶向下绘制,细节逐层增加:
- 上下文图把整个系统画成单个处理,周围是所有外部实体以及它们之间的数据流。它定义了系统边界。
- 下一层把这个处理分解为主要处理,加入数据存储,并保持相同的外部数据流。
- 更低的层次按需要继续分解单个处理。
混乱在于:有些教材和工具把上下文图称为“0 层”,把第一次分解称为“1 层”;另一些则沿用更早的结构化分析惯例,把上下文图看作独立的一层,把第一次分解称为“图 0”或“0 层”。两种说法都没有错。本文采用第二种惯例:上下文图,然后是 0 层,再是 1 层。无论你用哪种,都请在图上写明。
示例:一家网店
一家网店接收客户订单,通过支付服务商收款,并向负责发货的仓库发送拣货单。
上下文图
一个处理“0 Online store”,以及三个外部实体:
| 数据流 | 来源 | 去向 |
|---|---|---|
| Order details | Customer | Online store |
| Order confirmation | Online store | Customer |
| Payment request | Online store | Payment provider |
| Payment result | Payment provider | Online store |
| Pick list | Online store | Warehouse |
| Shipment status | Warehouse | Online store |
这就是从外部看到的整个系统。任何人一分钟就能看完,而它已经回答了重要的问题:哪些组织会收到客户数据,我们又依赖哪些系统。
0 层
把网店分解为三个处理和两个数据存储:
- Take order 从客户处接收订单详情,从 D2 Products 读取价格,把订单写入 D1 Orders,并把支付请求传给处理 2。
- Process payment 把支付请求发送给支付服务商,接收支付结果,更新 D1 Orders 中的订单,并把已支付的订单传给处理 3。
- Fulfil order 读取已支付的订单,把拣货单发给仓库,接收发货状态,并向客户发送订单确认。
上下文图中的每一条外部数据流都会在 0 层再次出现,并连接到相同的实体。这就是平衡,下面的图让它一目了然。
1 层
每个 0 层处理都可以继续分解。处理 1(Take order)的 1 层图如下:
| 处理 | 输入 | 输出 |
|---|---|---|
| 1.1 校验购物车 | 来自 Customer 的 Order details | 有效的购物车 |
| 1.2 计算订单价格 | 有效的购物车,来自 D2 Products 的商品价格 | 已定价的订单 |
| 1.3 记录订单 | 已定价的订单 | 写入 D1 Orders 的新订单,发给处理 2 的支付请求 |
1.1 到 1.3 的输入和输出合在一起,必须与 0 层中处理 1 的输入和输出一致。每个 1 层分解都画成单独的图,并在标题中写上父处理的编号。
平衡:评审者最先检查的规则
当流入和流出子图的数据流与流入和流出其父处理的数据流相同时,子图就是平衡的。如果 0 层出现了一条上下文图中没有的“退款申请”数据流,那么两者之一必有错误:要么系统存在一个没人认可过的接口,要么上下文图不完整。平衡让 DFD 的各层作为一个整体值得信赖,检查也很容易:把每一层的外部数据流并排列出来即可。
常见错误
- 画成了流程图。 判断、循环和表示“然后”的箭头属于流程图或 BPMN 图。DFD 展示的是数据,而不是控制流。
- 数据流没有名称。 没有标签的箭头对评审者毫无意义,要写明它承载的数据。
- 一张图里处理太多。 一层中超过大约七到九个处理就难以阅读。把它们分组,再在下一层展开各组。
- 只接收数据的数据存储。 如果从来没有处理读取某个存储,它为什么存在?如果从来没有处理写入,它的数据又从哪里来?
- 混用层次。 在同一张图上把详细的子处理和高层处理并列,会破坏层次结构。
DFD、流程图与架构图
| 数据流图 | 流程图 | 架构图 | |
|---|---|---|---|
| 展示 | 数据及其来源、转换和存储 | 步骤、判断和顺序 | 组件、部署和连接 |
| 时间与顺序 | 无 | 有 | 很少 |
| 最适合 | 需求、隐私评审、威胁模型 | 规程与算法 | 设计与运维 |
DFD 是威胁建模的标准输入:在 0 层图上加上信任边界,就有了 STRIDE 评审的基础,威胁建模数据流图一文对此有详细介绍。它们在隐私工作中同样有用,GDPR 数据映射图就是沿着同类数据流追踪个人数据。
DFD 分层图
图表内容
- Context diagram
- 0 Online store
- Customer
- Payment provider
- Warehouse
- Level 0
- 1 Take order
- 2 Process payment
- 3 Fulfil order
- D2 Products
- D1 Orders
- order details
- order confirmation
- payment request
- payment result
- pick list
- shipment status
- order to pay
- paid order
- prices
- new order
- payment status
这张图的读法如下:
- 外部实体位于中间:Customer、Payment provider 和 Warehouse。两张图都连接到它们。
- 左边的框架是上下文图:一个处理 0 Online store,与各实体之间有六条带名称的数据流。
- 右边的框架是 0 层:Take order、Process payment 和 Fulfil order,以及 Orders 和 Products 两个数据存储。
- 平衡清晰可见:左边的每条外部数据流都在右边再次出现,这次连接到负责它的那个处理。
- Markdown 块是图例和编号约定。
画出经得起评审的 DFD
- 每条数据流都用名词命名。 对 DFD 的评审意见大多与没有标签或标签含糊的箭头有关。
- 各层之间保持一致的编号,让读者能把 1.2 追溯到 1。
- 外部实体在每一层都放在相同位置,这样一眼就能看出是否平衡。
- 为你的记法加上图例,尤其是当你把经典符号和表示数据存储的圆柱体混用时。
关于其他图类型的记法,可参阅流程图形式的事件响应示例和BPMN 图示例。
在 Archboard 中构建
- 使用 Google 或 GitHub 登录,并新建一张图。
- 在中间一列添加三个基础节点包中的 方框 节点,分别表示 Customer、Payment provider 和 Warehouse。
- 在左侧添加一个名为 Context diagram 的基础 框架,在其中放一个基础节点包中的 椭圆 形状,表示 0 Online store。
- 在右侧添加第二个名为 Level 0 的 框架,在其中放三个 椭圆 形状表示各个处理,再放两个 圆柱 形状表示 D1 Orders 和 D2 Products。
- 把数据流画成带标签的连线,实体两侧使用相同的标签。
- 添加一个基础节点包中的 Markdown 块,写上图例和编号约定。
这个示例共用 12 个图表节点,在 Free 套餐的限制之内。
常见问题
数据流图的四种符号是什么? 外部实体、处理、数据存储和数据流。Yourdon 与 DeMarco 把处理画成圆形,把存储画成平行线;Gane 与 Sarson 使用圆角矩形和一端开口的矩形。
上下文图和 0 层 DFD 有什么区别? 上下文图把整个系统及其外部实体画成单个处理。下一层把这个处理分解为主要子处理和数据存储。有些资料把上下文图称为 0 层,所以要说明你采用的惯例。
DFD 中的平衡是什么意思? 流入和流出子图的数据流必须与其父处理的数据流一致。这样可以确保各层描述的是同一个系统。
DFD 和流程图有什么不同? DFD 展示哪些数据流向哪里、如何被转换,不涉及顺序和判断。流程图展示步骤的顺序以及步骤之间的判断。
一个 DFD 应该有多少层? 读者需要多少层就画多少层。大多数系统用上下文图和 0 层就能描述清楚,只有复杂或敏感的处理才需要 1 层图。