Better

Ethan的博客,欢迎访问交流

领域驱动设计实践

最近在设计一个后台管理系统时,在组织文件结构时,总是反复摇摆,偶尔间看到一篇我觉很棒,很有启发的文章,于是应用在项目当中,在这里简单总结一下

前端开发面临的问题

这里的问题并不是指技术细节上的困难,而是遇到高复杂度业务的开发困难

  • 业务逻辑本身错综复杂:思考如何将其直面地转换成更易理解的代码,让后续维护者阅读代码就能大致理解其业务逻辑概貌
  • 全局业务理解不够透彻:通常表现在人员变动
  • 知识在团队传播中的丢失:尽量将代码写成既能运行又能展示业务逻辑知识的形态
  • 团队无法形成统一逻辑代码书写规范:书写业务逻辑的位置、方式、分层、复用等

如果团队没有形成统一的代码书写规范,就容易形成垃圾桶现象,比如

  • 视图层过厚,存在过多的条件渲染,优化思路:视图层最好单一,数据在展示到视图层之前,做好数据的筛选、转换,判断逻辑抽象层工具函数方式 util 中
  • 判断逻辑重复,同样的逻辑在多个视图层中重复出现
    • 违反了代码重复原则,后期需要统一修改时,设计文件多成本大
    • 团队中各成员形成知识不同步,同样的功能 A B 都实现了,但互相却不知道,并且容易出现因实现方案不同导致的结果不一致的问题。
    • 优化思路:试图将某个实体抽象成一个类,对外提供一个函数用来进行逻辑判断,视图层统一调用该函数便能复用这块逻辑,并且容易理解其含义
  • 接口调用不统一
    • 存在问题:多块业务代码用到了同一个接口,并且在各自的根目录下都有一份相同的请求代码
    • 后果:代码重复,修改一块业务代码却找到了很多相同的请求逻辑,接口发生变化,统一维护的成本较大
    • 优化思路:将整个项目中所有的请求函数统一放在 common 进行管理,根据领域划分,同时接口函数尽量可配置、可扩展,供多个业务使用
  • 接口字段不可控
    • 存在问题:定义字段在理想的情况下是前端主导,且前后端有共同的认知,但是不排除特殊情况下接口字段定义混乱且不直观。
    • 后果:阅读代码时,接口字段不规范,在视图层展示时,会导致误解或者难以理解的代码逻辑。
    • 优化思路:将接口层抽离出来,在接口返回时,逐一将字段列举出来,将不符合规范的字段进行纠正,转换成更易理解的词语,甚至在这一层中就能将很多字段内容进行转换,比如后端返回的金额为分单位*100的整数,在这一层中即将其转换为浮点数,在视图层中即可直接使用。
  • 忽略业务整体
    • 问题:只知道自己负责的那几个页面,逐步恶化成 面向页面编程
    • 后果:重复性问题、缺少对 可扩展性预判未来性 考虑
    • 熟悉整体业务,会对其新的需求进行更深的思考,判断其对整个项目是否会有明显的“驱动”作用,而进一步考虑是否应该拒绝该需求或者提出更好的需求建议

领域驱动设计

从产品-后端-前端对其领域的划分认知都是一致的,这是各角色对其整个项目进行合作的基础,在一起讨论问题时,都知道对方讲述的信息是处在哪个域上。

在对领域具有统一认知的情况下,需求方也会更谨慎、清晰地提出新的需求或是更改业务逻辑,各方人员对其业务的熟悉后,也能从自己负责的职能角度上表达出自己对新业务新迭代的看法或建议,而不是“机械”地根据需求文档完成自己的职责。

  • 业务领域
    • 产品 => 后端 => 前端,三方大致统一领域划分后,各自对需求概念、名词认知统一,最后才进行代码的编写。
    • 领域模块图是需要各方人员进行持续维护演进的,其存在的意义是加强了成员对业务的理解,让团队成员力量进行聚焦,共同思考业务
  • 前端领域设计与结构分层
    • 视图层尽可能薄:获得的数据能够直接使用到视图层中,禁止在视图层中对数据进行转换、筛选、计算等逻辑操作。
    • 不写重复逻辑:遇到相同的逻辑尽可能复用而不是重写,逻辑函数尽可能写成可拓展可维护,暴露给团队其他成员。
    • 不同职责的代码进行分层:将不同职责代码合理分层,每层尽可能纯净,互不影响。
    • 前端字段不受后端影响:返回字段进行纠正,字段含义尽可能直观,在视图层使用时,能够更清晰地描述视图结构。
    • 可纵观全局领域:前端进行领域模块结构设计时,能够纵览整个项目下所有的领域,以及每个领域下具有的逻辑功能。
  • 结构图的分层思想,具体见最后的图,发现这种图超级赞

结构图的分层思想图 design.jpg

由于具体分层作用和解决的问题

  • 数据接口层
    • 数据请求层,负责 http 请求,是项目中唯一与后端服务进行交流的一层。
    • 分层作用:在这一层中集结了 interest 领域下所有的接口函数,避免了数据接口分散到各个页面,统一存放更易管理,解决接口调用不统一问题
    • 增加数据清洗层,可以做一些数据清洗的工作,负责将后端返回的数据“清洗”,改造成更直观地字段(key)、更方便使用的数据(value)。在这一层对接口字段、内容经过二次加工,避免了后端定义字段不规范、混乱对前端的影响,含义清晰、规范的字段在视图层使用时更具有表现力,这里我们解决了上文提出的接口字段不可控性问题。
  • 领域层
    • 领域层是整个项目的核心层,它掌管了所有领域下的行为与定义,它是整个项目中最能体现业务知识的一层。
    • entity:实体,是领域服务的载体,它定义了业务中某个个体的属性与方法,属性和方法的返回值主要是用于视图层中的直接展示,同一个实体的逻辑确保只在实体类中编写,在不同视图下可复用,解决了判断逻辑重复的问题。
  • 视图层
    • 书写交互逻辑、样式的一层
    • 只需要调用了领域的服务,将返回值直接体现在视图层中,无需编写条件判断、数据筛选、数据转换等与视图展示无关的逻辑代码

根据结构图的分层思想,设计如下项目文件结构

├── apis # 后台接口,领域划分,比如 user
        ├── user
                ├── user.ts
                ├── translators # 数据清洗,if need
├── assets # 静态资源
├── components # 公用组件
├── constants # 变量
├── environments # 多环境配置
├── hooks # 公用 hook
├── layouts # 布局组件
├── locales # 国际化
├── models # store(dva)
├── pages # 页面组件,根据领域划分,比如 user
    ├── user # user 领域
        ├── login # 登录页
            ├── components # 拆分更多子组件
            ├── services # 集中声明需要的服务
            ├── login.tsx # 组件
├── services # 根据领域划分,给组件提供服务
    ├── user
        ├── entities
            ├── user # 实体对象,建议使用 ES6 class,可以封装逻辑,将 view 变薄
        ├── userService # 暴露服务
├── utils # 公用函数
├── tests # 测试配置

实践过程的建议

理解领域驱动设计,领域驱动设计的初衷是将项目进行合理地结构分层,降低复杂项目的维护难度,有效地减少团队成员之间的协作成本,将业务直观地映射成代码,让开发者更关注业务整体的本身,不局限于自己的职责,共同提出更好的业务建议。

当团队中加入了新的成员后,先向他介绍我们项目下的领域模型,再分享我们的项目架构与分层。

严格的 Review

来源



留言