企业应用开发范式比较:数据驱动、特性驱动与领域驱动

在开发企业应用的时候,典型的开发范式基本上可以总结为三种:

  • 数据驱动:认为企业应用就是数据的存储和展示。其典型开发方式是“以数据库为中心的增删改查(CRUD)”,或者是Martin Fowler的《企业应用架构模式》中的“表模块”模式。
  • 特性驱动:认为企业应用是系统功能的集合,这些功能基本上是独立实现的。其典型的开发方式是Martin Fowler的《企业应用架构模式》中的“事务脚本”模式。
  • 领域驱动:认为企业应用像机器一样,由多个具有不同能力的零件(对象)组成,这些零件相互配合实现系统的功能。其典型的开发方式是Martin Fowler的《企业应用架构模式》中的“领域模型”模式。

下面先对它们作一个总结性的对比:

| |数据驱动 |特性驱动 |领域驱动|
|— |— |— |— |
|软件世界观|软件就是数据的存储和展示|软件是功能特性的集合|软件是由相互协作的智能零件组成的一台能动的机器|
|核心关注点|数据 |功能特性 |领域对象|
|核心模型|数据模型 /关系模型 |用例模型 |领域模型|
|业务逻辑组织典型模式|CRUD或表模块|事务脚本|领域模型|
|业务逻辑实现典型位置|数据库、表示层或缺失业务逻辑|应用层|领域层|
|重用价值|低 |较低 |高 |
|扩展成本|极高 |高 |低 |
|问题域/解决方案域语义距离|大|较大|小|

先做一个总结陈词(我不会为这个论断道歉):

在三种开发范式中,数据驱动最差,领域驱动最好,特性驱动介于两者之间。

很不幸的是:在当前企业应用开发中,数据驱动的CRUD方式占了统治地位。

1. 问题域简介

在本文中,我以一个银行账户应用开发为例,说明三种开发范式的典型实现方式以及它们的优缺点。

个人和机构都可能在银行开设一个或多个银行账户。账户的类型都很多种,我们这里只关注储蓄账户和信用账户两种类型。

对每种账户,都可以进行存款、取款或转账等操作,也可以查看当前余额和获取一段时间的对账单。

每种账户在取款或转账时都会有限额。对储蓄账户来说,限额就是不得超过当前账户余额;对信用账户来说,限额就是当前信用额度减去已经刷卡消费的金额。

储蓄账户和信用账户还有很多方面的区别,例如信用卡在取现是需要支付手续费等,但为了减少复杂性,我们的范例中先忽略掉这方面的内容。

下面我们用三种范式分别实现该问题域的解决方案,然后分析各种范式的优劣得失。为了便于比较,三种范式都采用相同的架构——N层应用架构。

2. 数据驱动范式

数据驱动设计范式的软件世界观认为:软件是用于处理数据的虚拟机器。软件开发的核心关注点应该是数据,软件的设计和构建应该围绕数据的存储、检索和展现来开展。

数据驱动设计范式以数据模型为核心和出发点来进行开发。如果采用关系数据库为数据存储媒介,软件的核心就是关系模型,通常表示为E-R图(实体-关系图)的形式。

数据驱动设计范式认为,对数据只有四种可能的操作:增(Create)、删(Delete)、改(Update)、查(Retrieve),简称CRUD。

2.1 数据模型

问题域的表述中有这样一种说法:“账户的类型都很多种,我们这里只关注储蓄账户和信用账户两种类型。”这里暗示了一个类型层级的存在:储蓄账户和信用账户都是一种账户。在“账户”这个概念层级上,它们有相同的行为——存款、取款、转账、获取余额、获取对账单;但在具体细节上,两种账户实现相同的行为时遵循不同的业务规则。

这里的一个重大选择是:

  • 只创建一个统一的账户(Account)实体(对应到数据库中的一张表),用一个鉴别列(例如名为CATEGORY的字符串型列)来区分储蓄账户和信用账户,或者:
  • 将储蓄账户和信用账户分别建模为独立的实体SavingAccount和CreditAccount,不将它们看成是同类的东西。

同样地,银行账户的拥有人可能是个人,也可能是公司、政府部门或其他机构。如果是个人,我们要记录他/她的姓名、身份证号码等;如果是机构,我们要记录其名称、所属地区等等信息。

在数据建模的时候,也有两种选择:

  • 只创建一个统一的Party实体,统一代表个人和机构。用一个鉴别列区分该Party是一个自然人还是一家机构。该表中有些列(例如id和名称)是个人和机构共有的;而一些列是个人特有的(例如性别、身份证号码等),一些列是机构特有的(例如所属地区、组织机构代码等)。或者:
  • 将自然人和机构分别建模为独立的实体Person和Organization,不将两者看成是同类的东西。

每个账户都属于一个持有人(Owner),该持有人可能是个人,也可能是机构。如果采用第一种方式,在Account表(或者SavingAccount和CreditAccount两张表中)只需要定义一个owner_id字段,指向Party表的主键,根据对应的Party的鉴别列可以知道持有人是个人还是机构。如果采用第二种方式,在Account/SavingAccount/CreditAccount表中必须定义三个字段:owner_person_id(自然人id)、owner_organization_id(机构id)和owner_type(持有人类型,有个人/机构两种选择)。如果持有人是个人,owner_person_id的取值为某个人的id,owner_organization_id的取值为null,owner_type的取值为PERSON。;如果持有人是机构,owner_person_id的取值为null,owner_organization_id的取值为某个机构的id,owner_type的取值为ORG。

由于

3. 特性驱动范式

4. 领域驱动范式