实体驱动设计:从功能堆砌到能力复用的架构演进

5 阅读11分钟

本文通过一个真实的需求演进案例,探讨如何通过实体驱动的设计思想,实现功能的高度复用与系统的可持续演进。

引言

在企业级应用开发中,我们经常面临这样的困境:随着业务需求的不断迭代,系统中出现了大量功能相似但又各自独立的模块。这些模块往往包含重复的筛选逻辑、列表展示和操作流程,导致代码冗余、维护成本攀升。

本文将从一个具体的需求场景出发,对比功能驱动设计实体驱动设计两种架构思路,从产品设计、交互设计、软件架构三个维度,系统性地分析实体驱动设计的优越性。


一、问题背景

1.1 需求场景

某数据处理平台存在一个"工单分发"模块,用于将待处理的工单批量分配给不同的处理团队。该模块最初设计为独立页面,包含基础的筛选条件和分发表单。

随着业务发展,用户提出了新的需求:

"希望在工单分发时,能够按批次进行筛选。"

这是一个看似简单的需求,但它引发了对系统架构的深层思考。

1.2 两种设计路径

面对这一需求,存在两种截然不同的设计路径:

路径 A:功能驱动设计

在现有的"工单分发"模块中新增批次筛选条件,保持模块的独立性。

路径 B:实体驱动设计

将"工单分发"视为对"工单"实体的一种批量操作,在"工单管理"模块中增加批量分发能力,复用已有的筛选体系。

本文将论证:路径 B 在产品设计、交互设计、软件架构三个层面均具有显著优势


二、核心概念

2.1 实体(Entity)的定义

在领域驱动设计(DDD)中,实体是指具有唯一标识符、贯穿整个生命周期保持身份连续性的领域对象。

判断一个概念是否为实体的三个标准:

  1. 唯一标识:具有业务意义的唯一 ID
  2. 生命周期:存在明确的状态流转(创建 → 处理 → 完成)
  3. 操作聚合:多个业务功能围绕其展开

以"工单"为例:

  • 唯一标识:工单 ID
  • 生命周期:待分配 → 处理中 → 已完成 → 已归档
  • 操作聚合:查询、分发、导出、归档、统计

2.2 功能驱动 vs 实体驱动

维度功能驱动设计实体驱动设计
组织原则按用户操作组织("分发工单")按业务实体组织("工单管理")
模块边界以功能为边界,边界动态扩展以实体为边界,边界相对稳定
能力复用代码级复用(复制粘贴)能力级复用(组合调用)
扩展方式修改现有模块新增操作插件

2.3 核心设计原则

实体驱动设计的核心可以概括为:

设计优越性 = 实体稳定性 × 操作可组合性 × 认知一致性
  • 实体稳定性:业务实体的核心属性相对稳定,不随功能需求频繁变化
  • 操作可组合性:筛选、选择、操作三种能力正交,可自由组合
  • 认知一致性:用户建立统一的心智模型,降低学习成本

三、产品设计层面的分析

3.1 需求本质的洞察

产品设计的首要任务是区分表面需求与本质需求

表面需求:"在工单分发模块增加批次筛选"
    ↓ 深入分析
本质需求:"能够按特定条件筛选工单,并进行批量分发"
    ↓ 抽象提炼
能力需求:筛选能力 + 批量操作能力

当我们将需求抽象到"能力"层面,会发现:工单管理模块已经具备完整的筛选能力,缺失的仅仅是"批量分发"这一操作能力。

3.2 能力的正交性设计

实体驱动设计的核心优势在于实现了能力的正交性(Orthogonality,指系统中的各个组件、功能或维度是相互独立的):

筛选能力 × 选择能力 × 操作能力 = 完整的批量处理能力

其中:
- 筛选能力:支持任意字段的条件组合
- 选择能力:支持单选、多选、全选、跨页选择
- 操作能力:支持分发、导出、归档等操作

正交性带来的直接收益是组合爆炸式的功能覆盖

筛选条件可执行操作功能驱动需开发实体驱动需开发
按批次批量分发✓ 需要✗ 已支持
按日期批量分发✓ 需要✗ 已支持
按状态批量分发✓ 需要✗ 已支持
按批次批量导出✓ 需要✗ 已支持
自定义条件批量分发✓ 需要✗ 已支持

3.3 需求响应效率对比

基于上述分析,两种设计在需求响应效率上存在显著差异:

功能驱动设计的迭代成本:

需求 1:按批次筛选后分发 → 开发周期 3 天
需求 2:按日期筛选后分发 → 开发周期 2 天
需求 3:按自定义条件筛选后分发 → 开发周期 5 天
累计:10 人天

实体驱动设计的迭代成本:

需求 1-N:使用现有筛选 + 批量分发 → 开发周期 0 天
累计:0 人天

四、交互设计层面的分析

4.1 心智模型的一致性

认知心理学研究表明,用户在使用系统时会建立心智模型(Mental Model)。一致的心智模型能显著降低认知负荷,提升操作效率。

功能驱动设计的心智模型:

用户需要建立多个独立的心智模型:

工单分发页面:
  └─ 特定的筛选条件 + 特定的操作流程 + 特定的反馈机制

工单导出页面:
  └─ 另一套筛选条件 + 另一套操作流程 + 另一套反馈机制

工单归档页面:
  └─ 又一套筛选条件 + 又一套操作流程 + 又一套反馈机制

实体驱动设计的心智模型:

用户只需建立一个统一的心智模型:

工单管理页面:
  └─ 统一的筛选体系
  └─ 统一的选择机制
  └─ 统一的操作入口
  └─ 统一的反馈规范

4.2 渐进式披露原则

优秀的交互设计应遵循**渐进式披露(Progressive Disclosure)**原则,根据用户的专业程度逐层展示功能复杂度:

Layer 1 - 基础层(所有用户)
  └─ 预设筛选条件 + 列表浏览

Layer 2 - 进阶层(熟练用户)
  └─ 自定义筛选条件 + 条件保存

Layer 3 - 高级层(专业用户)
  └─ 批量选择 + 跨页选择

Layer 4 - 专家层(管理员)
  └─ 批量操作(分发/导出/归档)

实体驱动设计天然支持这种分层,而功能驱动设计需要在每个独立模块中重复实现。

4.3 操作反馈的规范化

批量操作场景下,清晰的反馈机制至关重要。实体驱动设计可以建立统一的反馈规范:

批量操作反馈模板:

┌─────────────────────────────────────────┐
│  操作预检结果                            │
├─────────────────────────────────────────┤
│  已选择:100 条记录                      │
│  ✓ 符合条件:85 条                       │
│  ✗ 不符合条件:15 条                     │
│    - 状态不符:10 条                     │
│    - 已被处理:5 条                      │
├─────────────────────────────────────────┤
│  [继续操作 85 条]    [取消]              │
└─────────────────────────────────────────┘

这种统一的反馈模式可以复用于所有批量操作场景。


五、软件架构层面的分析

5.1 模块边界的本质差异

两种设计思维的根本区别不在于代码如何拆分,而在于模块边界的划定依据

功能驱动设计的模块边界:

系统模块结构:
├── 工单分发模块(独立页面)
│   ├── 筛选区域(仅支持:批次、状态)
│   ├── 工单列表
│   └── 分发表单
│
├── 工单导出模块(独立页面)
│   ├── 筛选区域(仅支持:日期、类型)
│   ├── 工单列表
│   └── 导出配置
│
└── 工单归档模块(独立页面)
    ├── 筛选区域(仅支持:状态、处理人)
    ├── 工单列表
    └── 归档确认

每个功能模块都是一个"孤岛",拥有自己的筛选逻辑、列表展示和操作流程。当需求变化时(如"分发时也要支持按日期筛选"),必须修改分发模块本身。

实体驱动设计的模块边界:

系统模块结构:
└── 工单管理中心(统一入口)
    ├── 筛选能力(支持所有字段)
    ├── 工单列表 + 选择能力
    └── 批量操作
        ├── 分发
        ├── 导出
        └── 归档

只有一个"工单管理"模块,所有针对工单的操作都是这个模块的"插件"。筛选能力是通用的,任何操作都可以基于任意筛选条件执行。

5.2 变更影响范围的对比

这种边界差异直接决定了需求变更时的影响范围:

需求变更功能驱动设计实体驱动设计
分发时增加日期筛选修改分发模块无需修改(已支持)
导出时增加批次筛选修改导出模块无需修改(已支持)
新增"批量转交"操作新建转交模块(含筛选、列表)仅新增转交操作
修复筛选组件 Bug需修改所有模块修改一处,全局生效
统一筛选交互体验需逐个模块调整修改一处,全局生效

5.3 开闭原则的自然体现

实体驱动设计天然符合开闭原则(Open-Closed Principle):

  • 对扩展开放:新增操作类型只需开发操作本身,无需触碰筛选和列表逻辑
  • 对修改关闭:已有的筛选能力、选择能力不因新操作的加入而改变

而功能驱动设计则相反:每次新增功能都需要"修改"——要么修改现有模块,要么复制现有代码创建新模块。

5.4 复用层次的差异

两种设计在复用层次上存在本质差异:

功能驱动设计的复用方式——代码复用:

分发模块的筛选代码 --复制--> 导出模块
分发模块的列表代码 --复制--> 归档模块
                    ↓
            代码散落在各处
            修改时需要同步多处
            容易产生不一致

实体驱动设计的复用方式——能力复用:

工单管理中心
    │
    ├── 筛选能力 ← 分发操作使用
    │            ← 导出操作使用
    │            ← 归档操作使用
    │
    └── 选择能力 ← 所有批量操作共用
                    ↓
            能力定义在一处
            所有操作共享同一实现
            修改自动全局生效

六、技术债务与长期演进

6.1 技术债务的累积模式

功能驱动设计的债务累积:

迭代 1:新增批次筛选 → 修改分发模块
迭代 2:新增日期筛选 → 修改分发模块
迭代 3:新增状态筛选 → 修改分发模块
迭代 4:新增导出功能 → 复制分发模块的筛选逻辑
迭代 5:修复筛选 Bug → 需要同步修改多个模块
...
技术债务指数级增长

实体驱动设计的债务控制:

迭代 1-N:所有筛选需求 → 复用工单管理的筛选能力
迭代 M:新增操作类型 → 仅开发操作本身
Bug 修复:修改一处 → 全局生效
...
技术债务线性可控

6.2 系统演进的可持续性

实体驱动设计为系统的长期演进提供了坚实基础:

Phase 1 - 单实体阶段
  └─ 工单管理:查询 + 选择 + 批量操作

Phase 2 - 多实体阶段
  └─ 抽象通用能力:EntityList + BatchOperations
  └─ 应用于:工单、订单、用户、资源...

Phase 3 - 平台化阶段
  └─ 配置化生成:基于实体元数据自动生成管理界面
  └─ 插件化扩展:操作类型可动态注册

七、如何判断应该采用哪种设计

7.1 适用实体驱动设计的场景

当满足以下条件时,实体驱动设计通常是更优选择:

  1. 存在明确的业务实体:系统中有清晰的、具有唯一标识的业务对象(如工单、订单、用户)
  2. 多种操作围绕同一实体:针对该实体存在查询、编辑、批量处理等多种操作需求
  3. 筛选条件具有通用性:不同操作场景下,用户可能需要相似的筛选维度
  4. 可预见的功能扩展:未来很可能新增更多针对该实体的操作类型

7.2 功能驱动设计仍有价值的场景

并非所有场景都适合实体驱动设计:

  1. 一次性或低频功能:如年度报表生成、数据迁移工具,独立模块更简单直接
  2. 跨实体的复杂流程:如涉及多个实体协作的审批流程,可能需要专门的流程引擎
  3. 高度定制化的交互:某些操作需要完全不同的交互模式,强行统一反而增加复杂度

7.3 决策的关键问题

在设计新功能时,可以问自己一个核心问题:

"这个功能的本质,是创造一个新的业务能力,还是对已有实体的一种新操作?"

如果答案是后者,那么应该优先考虑将其作为实体管理能力的扩展,而非独立模块。