# 贫血模型介绍

Jason
Table of Contents

避免贫血症和失忆症

贫血症

只有数据没有行为的对象 & 事务脚本大泥球

贫血模型

贫血模型指的是只有数据,没有行为的类。通常是数据库表结构的映射。贫血模型通常会造成代码有如下特征:

  1. BO 里面没有包含(或包含了很少的)业务逻辑,仅仅包含了 get/set 方法
  2. 业务逻辑被实现在 Service 和 Controller 里:比如校验逻辑、计算逻辑、格式转化逻辑、对象关系逻辑、数据存储逻辑等
  3. 代码中有大量的 Utils 工具类

贫血模型的缺陷

  1. 无法保证模型对象的完整性和一致性:因为对象的所有属性都是公开的,只能由调用方来维护模型的一致性,而这个是没有保障的,因为逻辑分散,开发者难以知其全貌
  2. 对象行为的可发现性极差:贫血模型对外暴露的只有对象的属性,但是单纯从属性上很难看出来哪些业务逻辑,什么时候可以被调用,以及可以赋值的边界是什么,业务逻辑不内聚,模型无法自解释
  3. 代码逻辑重复:比如校验逻辑、计算逻辑,都很容易出现在多个服务、多个代码块里。提升了维护成本,以及 bug 出现的概率。一类常见的 bug 就是当贫血模型变更后,校验逻辑由于出现在多个地方,没有能跟着变,导致校验失败或失效
  4. 代码的健壮性差:比如一个数据模型的变化可能导致从上到下的所有代码的变更
  5. 强依赖底层实现:业务代码里强依赖了底层数据库、网络/中间件协议、第三方服务等,造成核心逻辑代码的僵化且维护成本高

为什么会产生贫血模型

虽然贫血模型有很大的缺陷,但是在我们日常的代码中,却经常产生贫血模型。以下是造成这种现象的几点原因:

  1. 数据库思维:从有了数据库的那一天起,开发人员的思考方式就逐渐从“写业务逻辑“转变为了”写数据库逻辑”,也就是我们经常说的在写 CRUD 代码
  2. 贫血模型“简单”:贫血模型的优势在于“简单”,仅仅是对数据库表的字段映射,所以可以从前到后用统一格式串通。这里简单打了引号,是因为它只是表面上的简单,实际上当未来有模型变更时,你会发现其实并不简单,每次变更都是非常复杂的事情
  3. 脚本思维:很多常见的代码都属于“脚本”或“胶水代码”,也就是流程式代码。脚本代码的好处就是比较容易理解,但长久来看缺乏健壮性,维护成本会越来越高

但是可能最核心的原因在于,实际上我们在日常开发中,混淆了两个概念:

  • 数据模型(Data Model):指业务数据该如何持久化,以及数据之间的关系,也就是传统的ER模型
  • 业务模型/领域模型(Domain Model):指业务逻辑中,相关联的数据该如何联动

所以,解决这个问题的根本方案,就是要在代码里严格区分 Data Model 和 Domain Model,具体的规范会在后文详细描述。在真实代码结构中,Data Model 和 Domain Model 实际上会分别在不同的层里,Data Model 只存在于数据层,而 Domain Model 在领域层,而链接了这两层的关键对象,就是 Repository

失忆症

如果我们不断维护领域模型,那么他将承载软件系统所有的功能信息。团队的所有成员都可以轻易地通过可视化的领域模型来获取领域知识。

但如果没有一个地方来维护领域知识,当开始编写软件时,其实我们所知甚少。项目知识零散地分散在 PRD 和技术文档中,其中夹杂着其他一些无关信息。甚至,有些 PRD、技术文档、与代码是各不相同的,因此我们甚至不知道哪些知识是真正需要的知识。

所以,开发人员只能通过反复阅读大量的代码来试图理解历史的逻辑。如果只能通过这种方法才能梳理出系统的全包,这体现出我们的系统的复杂度非常高。

看起来没什么技术难度的领域很可能是一种错觉——我们并没意识到不知道的东西究竟有多少。这种无知往往会导致我们做出错误的假设。

同时,所有项目都会丢失知识。已经学到了一些知识的人可能干别的事去了。而且当使用典型的设计方法时,代码和文档不会以一种有用的(可理解的)形式表示出这些来之不易的知识。因此,一旦由于某种原因人们没有口头传递知识,那么知识就丢失了。

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts