浅谈领域驱动在前端的应⽤

简介: 领域驱动是⼀种思想,不仅可以应⽤于软件开发,也没有绝对的开发规范,适合⾃⼰的业务和团队背景就好,我们不是为了应⽤⽽应⽤,⽽是为了解决问题。

领域驱动介绍

DDD 这个词⼉,来⾃ Evans Eric 在 2003 年的⼀本书《Domain-Driven Design: Tackling Complexity in the Heart of Software[1]。

在这本书中,Evans 提出了他对软件的复杂性来源的⼀个关键洞察——软件模型跟领域模型的不匹配,并提出他的解决⽅案(即 DDD)。

软件模型即我们开发对业务理解之后划分的代码组织的模型,领域模型则来源于领域专家(或产品同学),⼀般来说,后端同学更加注重开发模型的划分,但也会存在与领域专家划分的模型出现出⼊的情况。

领域

领域具体指⼀种特定的范围或区域,也就是业务范围。对于如对⼀个图研发平台来说,业务范围就是图业务相关的图数据、图模型、图计算等。那领域模型如何划分呢,这就涉及到领域驱动的核⼼知识体系了。

核⼼概念

具体包括:领域、⼦域、核⼼域、通⽤域、⽀撑域、限界上下⽂、实体、值对象、聚合和聚合根等概念。下⾯这张图可以很好地体现他们的关系:

image.png

其实我们不必过多纠结这些概念到底是做什么意思,除了⽀撑域、 通⽤域和核⼼域,其余的概念可以⽤⼀个关键词来概括:边界,只是不同边界的业务范围⼤⼩不同。


⽀撑域、 通⽤域和核⼼域

核⼼域

决定产品和公司核⼼竞争⼒的⼦域是核⼼域,它是业务成功的主要因素和公司的核⼼竞争⼒。例如在图研发平台中,图项⽬、图计算、图数据、图配置、图运维等是图研发过程的核⼼内容,应该划分到核⼼领域。

⽀撑域

不包含决定产品和公司核⼼竞争⼒的功能。例如在图研发平台中,收藏中⼼的功能是⽤来收藏图模型、图查询语句等的,这种功能对于图平台的⽤户⽽⾔,很明显不是核⼼的功能,类似这样的功能就可以划分到⽀撑域。

通⽤域

没有太多个性化的诉求,同时被多个⼦域使⽤的通⽤功能⼦域是通⽤域。例如在图研发平台中,权限系统、⽇志系统、⼯单系统等是所有⼦域都可能需要的基础通⽤能⼒。

⼦域

以上的边界范围和⼦域相同,⼦域并不是固定的,可以根据具体的业务情况进⾏划分。


领域划分

确定好核⼼域、⽀撑域和通⽤域之后,便可以对⼦域下的内容进⾏进⼀步的划分,即界限上下⽂、聚合和实体。

界限上下⽂

每个⼦域下可以有多个界限线上下⽂,限界上下⽂定义了⼀定业务范围的边界,确保每个上下⽂含义在它特定的边界内都具有唯⼀的含义,例如图研发平台的图项⽬是核⼼领域下的⼀个界限上下⽂,那就意味着所有跟图项⽬相关的业务内容,例如图项⽬信息、图项⽬列表、图项⽬的创建等,都可以且仅可以在图项⽬内找到。

聚合和实体

每个界限上下⽂下可以有多个聚合,聚合由多个实体组成,实体是多个属性、操作或⾏为的载体,例如图研发平台的图项⽬下图项⽬信息确定为⼀个聚合,项⽬的信息就可以作为这个聚合的实体,⾥⾯可以包含项⽬信息的属性定义、获取项⽬信息的⽅法等。

领域模型

通过领域的划分,我们将得到⼀个领域模型,这个模型即业务的知识体系,有了这个模型我们可以获得什么好处呢?

  • 可以通过领域模型清楚地看到业务的全貌
  • 统⼀语⾔,领域的划分过程是产研团队⼀同参与的,⼤家对领域边界,尤其是界限上下⽂的认知⼀定是统⼀的,所以研发说的图项⽬信息,⼀定和产品说的图项⽬信息是同⼀个“图项⽬信息”。


如何驱动

有了领域模型已经可以获得⼀定的收益,但这并不是我们的Y终⽬的,我们希望可以通过领域模型来驱动我们进⾏软件的开发,其实开发模式并不固定,适合就好。接下来会详细讲解在图研发平台中我们是如何借鉴领域驱动思想进⾏前端开发的。

⾯临的问题

为什么要⽤借鉴领域驱动的思想,⾸先看下图研发平台前端⾯临的问题:

image.png

业务逻辑复杂

有些业务场景确实本身就很复杂,⾯对复杂的业务,会有如下问题:

1. 如何合理地将业务进⾏拆分,从⽽降低代码实现的复杂度,且保障后续的易维护性。

2. 新⼈⾯对复杂地业务如何快速了解,如何快速适应开发且能保证开发质量。

业务理解不够深⼊全⾯

我们常常会按⻚⾯或者模块分配任务,短⽽快的迭代节奏,使得被分配到任务的同学很难对其他同学所做的模块有较深⼊的了解,后续可能也不会去看其他同学的代码,每个⼈接触的业务都是被切分的,这种形式不利于组内同学对业务的理解。

难形成统⼀逻辑代码书写规范

这⾥的规范是实现业务逻辑的位置、⽅式的规范,在不加以约束的情况下,我们很容易看到业务数据的处理遍布视图层,并且实现⽅式⼜很多样,如dva,hooks等,这会导致视图层变得厚重,UI交互等逻辑代码耦合这⼤量的业务数据处理的代码,牵⼀发⽽动全身,我们很难看清业务数据处理的整个过程,不仅不易迭代,⽽且这样的代码迭代起来很容易出问题。

CR成本⾼、不易测试

业务逻辑的复杂性,拆分的不合理,代码不规范等⼀系列问题,导致我们CR效率和质量都会打折扣,单测也变得⽆从下⼿,很难起到质量把控的作⽤。

⽬录结构划分不合理不利于复⽤

⻚⾯维度即产品⻚⾯或者UI设计⻚⾯,对于业务系统的开发,通常我们习惯以⻚⾯维度组织代码,将⻚⾯⾥的组件进⾏拆分,这样开发起来很直接,但是会引发如下问题:

1、Component1⼀开始被划分到Page1,但是后来发现Page2也需要,Component1继续划分在Page1就不合理了,可能需要重新进⾏⽬录划分,或者组件的提取。

2、PageX是⼀个新的⻚⾯,新的⻚⾯也会⽤到Component1,但是开发者并未参与过Page1和Page2的开发,开发者对Component1的复⽤变得不可控。

image.png

多版本代码不易维护

如果仓库是多版本,⽐如存在主站版本和商业化版本,版本之间的核⼼功能基本是⼀致的,但是⼀定会存在差异,⼀⽅⾯要对相同功能代码的同步,⼀⽅⾯⼜要保持彼此之间的差异,以上的⼀系列问题,使得这种场景变得很困难。

透过现象看本质,以上问题其实很⼤程度都跟“边界”有关,前端代码的边界确实让⼈难以把控,⽽领域驱动⼗分擅⻓解决“边界”的问题。


结合领域解决问题

以领域维度组织代码

通过领域划分得到图研发平台的领域模型

image.png

我们将项⽬的⽬录结构域领域模型相对应进⾏划分

image.png

components为公共组件,可以被所有聚合引⼊,pages为⻚⾯组件,domains即代表⼦域,如 domains-core代表核⼼域,核⼼域下graph-project、graoh-config等为界限上下⽂,代表图项⽬、图配置等,graph-project下的project-info、project-list等则对应聚合,聚合下的内容:

image.png

components

该聚合下的组件,该⽬录下的组件,除了公共只能引⼊该聚合⽬录下的内容,也就是说组件在聚合内是⾃闭环的。

entities

entities下可以定义多个实体,每个实体内都声明了该实体的属性和⽅法,如project-info.ts我们看到ProjectInfoEntity⾥定义了该实体的属性,以及获取属性的⽅法和更新属性的⽅法,为了保证代码语义updateProjectInfoEntity⽅法是不允许暴露出去的。

entity所定义的属性,是在视图层直接进⾏消费的,不需要做数据转换的。

constants

该聚合下⽤到的常量,这⾥可以统⼀书写规范,例如只能⼤写字⺟加下划线。

services

该聚合下⽤到的后端接⼝服务,定义为⼀个类,如:service类起到如下作⽤:

1、声明了该聚合下⽤到哪些服务,这些接⼝服务的⼊参和出参都是明确的,将来如果涉及到接⼝变更或替换,可以直接在这⾥做变更,尽可能减少视图层的变更。

2、规范后端接⼝的命名。

translator

上⾯提到,我们要求entity的数据是视图层直接消费的,后端的数据很多情况下是要做转换的,这就需要 translator,将后端接⼝数据转换为视图层可以直接消费的数据。

transformer

还有⼀类数据是前端提交到后端的,典型的如表单场景,可能也会涉及到数据的转换,将前端提交的数据转换成后端接⼝接收的数据。

image.png

通过transformer和translater,可以减少在视图层进⾏的业务数据处理,理想的状态是视图层只有展示和交互逻辑的代码书写。

*这⾥的视图层包括pages和components。

index

可以理解为聚合根,外部只能通过聚合根来访问该聚合下的内容,也就是说,index内需要定义projectinfo这个聚合根下,哪些内容可以被外部访问到,这⾥我们就可以指定⼀些规则,来限制聚合可以被访问的内容,这样做可以⼀定程度保证代码的安全性,例如有些组件和⽅法只是聚合为聚合内部服务的,并不希望被外部访问到,就可以不对外暴露,避免后续在维护聚合内部业务的时候,引发外部问题。

*以上内容在聚合⽬录下的引⼊都是⾃闭环的,也就是说,除了全局的公共组件和公共⽅法可以引⼊,不能依赖其他聚合的内容。

contextService

理想情况下,每个聚合之间都是完全⾃闭环的,但是对于复杂的前端业务系统⽽⾔,⼀个聚合内的组件很难做到完全独⽴,我们不是为了应⽤领域驱动⽽应⽤,⽽是希望可以通过领域驱动解决我们的问题,要适合⾃⼰的业务和团队,对于components,我们允许引⼊其他聚合的内容,但是必须要在contextService内引⼊,也就是说,实体内的其他组件只能通过contextService获取到,这样可以很清楚地看到当前聚合的依赖,在改动聚合内容的时候,需要充分check对其他聚合的影响,我们也可以添加规则,来限制依赖的内容,如只能依赖聚合的components等。

当然聚合⽬录下还可以有其他的⽬录,如hooks、utils,可以按需添加。

引⽤领域代码拼装⻚⾯

完成领域代码就可以“拼装⻚⾯了”,只需要看当前开发的⻚⾯,⽤到哪些聚合的内容,在⻚⾯引⼊代码组装,此时,⻚⾯内只有⻚⾯视图层的逻辑了,数据的获取通过聚合的entities,组件也都来⾃各个聚合。

image.png

调整开发思维

前端开发⽐较常规的开发模式,是围绕⻚⾯展开的,以UI的维度将⻚⾯的组件模块进⾏拆分,⽽现在的开发流程:

团队划分领域模型,统⼀领域模型术语

开发前,产品、前端、后端等⻆⾊需要⼀起对当前需求进⾏领域划分,每个⼈都要参与其中,加深对业务的理解。

领域代码开发

开发⼈员按照上述开发规范,进⾏领域代码的开发。

拼装⻚⾯

开发⼈员根据⻚⾯中⽤到的领域信息,组装领域代码,完成⻚⾯开发。


整体开发流程

image.png


收益

项⽬⽬录就是领域模型,就是PRD

新⼈友好

新⼈只需要了解业务的领域模型划分,以及项⽬⽬录和领域模型的对应关系,便可以对项⽬代码有整体了解,即使上⼿开发⼀个新的功能,也可以很容易地复⽤以往实现过的核⼼业务数据、组件、⽅法等,且整体⽅案没有太偏技术的应⽤,新⼈可以很快进⼊开发迭代节奏。

视图层变得轻薄

这主要得益于transformer和translator。

⻚⾯测试的边界清晰

⻚⾯内涉及到的实体⼀⽬了然,且实体内的⽬录职责边界⼗分清晰,对于前端对业务⻚⾯的测试是很友好的。

降低CR成本

通过代码⽬录的变更,便可以清楚地知道改动点涉及的业务范围,以及代码变更是否符合该⽬录下代码的规范,业务数据的处理不会分散在各个⻆落了,可以很直观地看到整体数据的处理过程。

组件的复⽤变得简单

复⽤业务组件的思路已经不⼀样了,⼀个全新的⻚⾯开发,第⼀时间考虑的是⻚⾯中涉及到领域模型中的哪些⼦域以及实体,开发的时候会先去对应的实体⽬录去找是否有已经实现过得组件、⼯具等,这样即使⼀个对项⽬整体代码不是很熟悉的开发者,同样可以很轻松地对已有的实现进⾏复⽤,并且随着开发量的增加,对每个⼦域和实体都会有所了解,⽽不是只专注于⾃⼰开发的⼏个⻚⾯

代码⻛格变得统⼀

实体下每个⽬录的职责是清晰的,且每个⽬录下代码的⻛格是明确的,即使⼀个新⼈维护,照葫芦画瓢也不会出现与团队⻛格相差较⼤的代码,这样也带来了好的维护性,有利于项⽬的⻓期迭代。

开发者对业务的理解更加全⾯深⼊

随着产研每次对领域的讨论实体的划分,开发者会更有参与感,前端同学也会对后端接⼝设计有更多的了解,按照领域驱动思想进⾏开发的过程中,也会更加深⼊地理解各个领域和实体的功能,这种理解的加深会随着开发时间的推移变得范围越来越⼴,直⾄对全局业务都有更加深刻的理解,这种收益是⻓期的。

为多版本的维护打下基础

多版本产品之间虽有功能上的差异,但是它们的共同点是:领域模型具有⼀致性,即实体可能存在差异,但⼤的领域划分,尤其是核⼼领域和通⽤领域的划分,基本是不会发⽣变化的,这也意味着,我们在复⽤代码的时候,不需要再将组件作为Y⼩单位进⾏拆分共享,⽽是以实体作为Y⼩单位进⾏复⽤,配合Bit⼯具,进⾏源码级复⽤,可以极⼤提⾼开发效率,并且满⾜不同版本之间的不同需求。


没有绝对,适合就好

领域驱动是⼀种思想,不仅可以应⽤于软件开发,也没有绝对的开发规范,适合⾃⼰的业务和团队背景就好,我们不是为了应⽤⽽应⽤,⽽是为了解决问题。作为前端开发者,对于领域驱动的理解和应⽤仍是在实践和探索中,如有错误或表达不当之处,欢迎探讨指正。

[1] Eric Evans Domain-Driven Design –Tackling Complexity in the Heart of Software  


作者 | 轩骞

来源 | 阿里云开发者公众号


相关文章
|
6月前
|
开发者 容器 Docker
JSF与Docker,引领容器化浪潮!让你的Web应用如虎添翼,轻松应对高并发!
【8月更文挑战第31天】在现代Web应用开发中,JSF框架因其实用性和灵活性被广泛应用。随着云计算及微服务架构的兴起,容器化技术变得日益重要,Docker作为该领域的佼佼者,为JSF应用提供了便捷的部署和管理方案。本文通过基础概念讲解及示例代码展示了如何利用Docker容器化JSF应用,帮助开发者实现高效、便携的应用部署。同时也提醒开发者注意JSF与Docker结合使用时可能遇到的限制,并根据实际情况做出合理选择。
59 0
|
7月前
|
前端开发 开发者
领域驱动使用问题之在图研发平台中,领域驱动设计是如何帮助解决前端开发面临的问题的
领域驱动使用问题之在图研发平台中,领域驱动设计是如何帮助解决前端开发面临的问题的
EMQ
|
网络协议 前端开发 Linux
Neuron Newsletter 2022-06|新增 1 个南向驱动、开源前端代码
六月,我们发布了 Neuron 2.1.0 版本,这个版本主要与 eKuiper 进行了深度集成,可一键部署携带数据处理功能的 Neuron。
EMQ
213 0
|
4月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
306 14
|
4月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
84 0
|
4月前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
102 6
|
4月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
102 1
|
4月前
|
机器学习/深度学习 弹性计算 自然语言处理
前端大模型应用笔记(二):最新llama3.2小参数版本1B的古董机测试 - 支持128K上下文,表现优异,和移动端更配
llama3.1支持128K上下文,6万字+输入,适用于多种场景。模型能力超出预期,但处理中文时需加中英翻译。测试显示,其英文支持较好,中文则需改进。llama3.2 1B参数量小,适合移动端和资源受限环境,可在阿里云2vCPU和4G ECS上运行。
220 1
|
4月前
|
前端开发 算法 测试技术
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
本文对比测试了通义千文、文心一言、智谱和讯飞等多个国产大模型在处理基础计数问题上的表现,特别是通过链式推理(COT)提示的效果。结果显示,GPTo1-mini、文心一言3.5和讯飞4.0Ultra在首轮测试中表现优秀,而其他模型在COT提示后也能显著提升正确率,唯有讯飞4.0-Lite表现不佳。测试强调了COT在提升模型逻辑推理能力中的重要性,并指出免费版本中智谱GLM较为可靠。
125 0
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
|
5月前
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架

热门文章

最新文章

  • 1
    【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
  • 2
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 5
    详解智能编码在前端研发的创新应用
  • 6
    巧用通义灵码,提升前端研发效率
  • 7
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 8
    智能编码在前端研发的创新应用
  • 9
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 10
    抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目