阅读 1063

领域驱动设计在前端的应用

长期以来前端只关注于页面展示和交互,而如今前端承担了越来越多的业务逻辑,将领域驱动设计应用于前端十分有必要。


前端的痛点


  1. 软件开发需要考虑的因素太多:后端的数据库,环境,界面交互,不同客户端,鉴权,经常变动的需求等,难以将精力集中在核心的业务逻辑上。
  2. 模块边界不清晰,牵一发而动全身。经常变动的业务需求经常导致核心逻辑受影响。
  3. 一条业务流串联了多个页面、多个模块,或者一个页面、一个模块承担了多条业务流程,是严重的耦合。
  4. 每个开发者只清楚自己开发过的代码,但是所有人的知识聚合在一起却拼不出一个完整的应用。没有人真正清楚所有的业务逻辑。
  5. 前端业务代码基本没法测试。


领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的。


领域驱动设计(
domain-driven design,
DDD)不是编程框架或设计模式,而是一种方法论(Methodology)。



一些基本原则

  1. 抽象:领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分。
  2. 技术无关:领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等。
  3. 集中:领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助。
  4. 易实现:领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造。
  5. 协作:领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求。
  6. 表达:为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型。


领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分。设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化。


为了方便讨论,先明确一些领域驱动设计中的基本概念:

领域模型中的一些基本概念

  1. 实体-Entity
  2. 聚合-Aggregate
  3. 值对象-Value Objects
  4. 资源库-Repository
  5. 领域服务-Domain Services
  6. 领域事件-Domain Events
  7. 模块-Modules
  8. 工厂-Factory
  9. 防腐层-Anti-Corruption
  10. 传输对象-Data Transfer Object (DTO)




1、实体 Entity

实体是基本的领域单元,领域中的实体概念都可以表示为一个实体,例如一个商品,购物车,一个活动。

实体包含自身的一些属性和行为,可以实现为面向对象中的一个对象。

一般一个实体在数据库中单独建一个表。


2、聚合 Aggregate

多个相关的实体构成一个聚合。

例如一个商品包含供应商信息,类别等。

在数据库中体现为关联表。

一个聚合包含一个根和边界,上例中,商品、供应商都是不同的实体,但在商品这个聚合中,商品就是根。

边界的意思是,一个外部对象想要访问聚合中的实体,必须通过根实体。


3、值对象

只有特定的值没有独立含义的对象。例如一个用户的地址,可以用一个对象表示,但需要依附于其他实体存在。

在数据库中通常是一个表中的几个字段。


4、资源库

存储实体和值的地方,一般是和数据库交互,对应前端就是接口层。


5、领域服务

服务只包含逻辑而不包含数据,用来协调和处理多个实体之间的关系。


6、领域事件

领域模型层面定义的事件,多用于领域间或领域和外部系统交互。


7、模块

一般意义上的模块。多个实体、资源库、领域服务可以组成一个模块。


8、工厂

一些复杂的实体不能简单的实例化,需要工厂方法来组装。

例如一个商品实例,不仅需要查到商品本身的信息,还需要查到相关的优惠,以及购物车中的数量,才能拼装成一个可用的实体。


9、防腐层

由于领域是抽象的、独立的,不能强依赖于外部系统,和外部系统交互时需要注意处理接口兼容。

防腐层的作用是将外部系统的数据模型转换为领域内部模型,并针对可能的外部异常做好防御处理。


10、传输对象(DTO)

用于网络传输的对象,一般用于领域间数据交互。


领域模型的几种模式




前端的具体实践

1、充分沟通,建立领域模型

领域模型可以是prd,设计图,伪代码,是一种大家都理解、认可的功能描述。

前端需要充分参与项目的设计阶段,了解完整的相关领域知识。由于前端处于整个应用的展示层,是最直接体现系统业务功能的部分,因此前端应当对系统体系有全面的掌控。

2、领域模型应和框架无关

领域模型的构建应当是纯js或ts代码,不依赖于任何特定框架。现有的前端开发框架vue react 等应该只负责展示和交互。

这样的好处是方便单元测试和项目迁移,甚至可以将领域模型直接迁移到node层。

3、领域解藕

相对独立的领域模块尽量不要发生直接的引用关联,即使是通过依赖注入。尽量在领域之上的业务层,或者通过领域事件来产生关联。

4、仓库、防腐层收口

领域实体模型、模块以及领域内的服务不应直接和后端、native等外部系统交互,应当由仓库层、防腐层统一处理。

领域内模型只关注业务逻辑。

这也解决了一个长久以来的前后端协同的问题。以往的开发中前端依赖于后端提供的接口和数据模型,而现在这部分工作由防腐层统一处理,前端和其他依赖方(主要是后端)可以相互独立的定义模型和开发自身的逻辑功能,调试行为也可以较为集中。

5、代码文档化

经过精细领域划分后,具体实现的领域代码已经可以比较直观的反映出业务逻辑,而不是像之前为了搞清楚一个功能需要去不同页面不同代码模块来回查找。应当对领域模型的代码编写较为详细的文档和注释,以更好的理解。

如果你恰巧精力充沛,写个单元测试更好。经过领域抽象的代码更容易写测试。

6、领域owner

建立好的领域应当有固定的领域owner,以保证领域知识的完整和传承。领域owner应当参与所有和领域相关的需求、设计、开发、review。由于前端代码的分散性,不固定的owner会导致领域设计被打乱。

7、代码Review

主要是为了使编写的代码和之前建立的抽象模型保持一致,以及成员之间跨领域的相互了解。

8、一些问题

  • Vuex、redux等应当属于业务层而不是领域层,不要引入到领域模型,会导致领域层产生复杂的依赖。
  • 一般不要把复杂的领域实体模型应用到vue的data对象上,因为vue会对data对象中的数据做响应式处理,过于复杂的对象会带来性能开销。用来数据渲染的对象最好是简单对象。


未来的实践之路

尽管基本理念相同,前端的领域驱动设计的具体实践会是一条同传统后端领域设计不同的道路,而且目前处于发展的初始阶段。现有的一些理念不一定是适合前端所有场景,搞得不好会把代码搞得更复杂,产生很多不必要的麻烦。

未来应该会产生一些相关的开发框架和工具来简化领域设计开发的难度。


参考文章

juejin.im/post/684490…

www.imooc.com/article/348…