关于 DDD 对前端的指导工作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于 DDD 对前端的指导工作相关的知识,希望对你有一定的参考价值。

参考技术A

什么是 Domain ?如何 driven design ?这些太过拘于概念,我们以一些实际的例子,来看看, DDD 是一种什么思想。

以个人为例,我们拥有年龄、身高、体重这样的属性,先排出其他影响因素,我们假设身高和体重只受到年龄影响,由此我们可以进行如下设计,在 class Person 中,包含三个 Property 分别是 age 年龄、 weight 体重, height 身高。

通常来说,在年龄的增长过程中,体重、身高会有不同阶段的变化,我们假设变化的公式为 weight = f(age) 和 height = f(age) 则有下面序列图

也就是 weight 和 height 的变化是受 age 影响的,那么如果我们统一封装在 Person 类中,这个类的领域就会变得很奇怪,所谓基本的属性,为什么类本身会需要对基本的属性有方法去处理变化呢?

领域驱动设计在这里引入了 值对象 的概念,根据 单一职责 迪米特法则 ,把 weight 和 height 这样的具有变化特点的值,转为对象的形式,也就是面试对象三大特点之一的 封装 ,所谓 封装变化

看到这里,你们是否对 DDD 领域驱动设计有一定了解了呢?

个人拙见:值对象的概念是 单一职责 迪米特法则 的合集,更注重颗粒度,无疑,这种做法实现了面向对象的高内聚低耦合,但是同样的,在内存开销和项目初期增加了巨大的问题。

领域驱动设计是一种大型项目构建比较推荐的设计,通过这样的设计,我们可以对项目形成较好对管理成本,接手项目的时候,可以对最小颗粒进行版本迭代和更新从而产生最小的影响。

这不是必须要求你的项目就一定要这样做

因为初期成本太高,不适合初创型公司以这样的形式去设计项目。

很多时候,作为一个前端开发,我们会以页面作为最小颗粒,也就是在设计的时候,我们会认为一个页面就是一个 实体 (实体可以理解为值对象的集合),一个 menu 的模块,就是 聚合 (聚合可以理解为实体的集合)。在理解上面这两个名词之后,我们从最小颗粒(页面)开始看起。

页面通常包含 N 个组件,在 React 或者 Vue 中,我们使用 Ant Design 或者 ElementUi ,这里我仅用 React 举例。

一个 React 的页面 Container ,是一些 Component 的合集,我们把页面的变化进行了封装,因为我们认为业务的变化会产生页面的变化。这一点没错,但是,相反的,我们去详细的说应该叫 业务的变化会引起部分页面中组件的变化 ,我们一概而论的把变化封装在了更高一层的页面上,实际上就是没有进行良好的组件拆分。

那么我们继续解耦,我们将每一个 Component 作为一个值对象去看,这样在业务变化的时候,我们就可以进一步的去修改特定的组件。

那么,领域驱动设计到此为止了吗?

在我看来,这是开始。

从第二节看下来,通篇的文字都是业务的变化,这就是最大的问题。我们所有的设计,都是基于业务。这并不符合 DDD ,因为 DDD 是领域驱动设计,也就是在 DDD 看来,设计的根本是因为领域。那么领域是如何去定义,我们就需要进行探讨了。

我们还是看一个比较常见的场景。

在上面这个需求里,我们可以看到有 Select 和 Table 构成的一个 Component ,如果我们将此 Component 作为值对象的话,如果我 Select 区域要新增一些条件,或者 Table 需要有改动,进行的修改,都是需要在此 Component 上,所以,在 DDD 中,我认为,构成的最小颗粒应该是由 UI 库提供的组件是值对象,而我们的 Component 级别的封装应该是 实体 ,构成的页面应该是 聚合

讲到这里,我希望你有所收获,无论是你对 DDD 领域驱动设计的不屑一顾,还是你从中有了新的见解。

之后,我要开始讲一些 奇怪 的东西了。

刚刚我们不断的在说, DDD 是在进行封装最小变化,那么问题来了, <Table /> 这样的组件,具有可扩展性吗?所有的可拓展性,都是基于样式层面的调整,如果我想对表格的渲染过程有不同的设计呢?如果我对表格的渲染需要惰性分页,滚动分页呢?你会发现,并不支持,所以,为什么中台系统是 ant design 这类 UI 库主打,是因为通用性极强,此类库提供了“最佳”实践,特殊的需求要自己写。看起来没什么问题,但是在我看来,不具备阔拓展性。

所以我希望的,或者我所设想的组件库,应该充分利用 extends ,达到如下的效果:

也就是我们不在自己构建一个全新的值对象,而是在通用的基础上,去继承并极大的增加复用性。

以上都是个人的一些见解啦, DDD 是一个还在摸索阶段的东西,来吧,关注 ihap 技术黑洞,我是 ihap肥少。

战术设计DDD

摘要

本篇是DDD的战术篇,也就是关于领域事件、领域对象、聚合根、实体、值对象的讨论。也是DDD系列的完结篇。
这一部分在我们团队争论最多的,也有很多月经贴,比如对资源库的操作应该放在领域服务还是领域对象中。
聚合根应不应该暴露给外部,还是要转成DTO。这些问题我们讨论了大半年,最后大家基本达成了共识,在当前的业务规模下,
这些问题没那么重要,可东可西。不会对代码的质量有啥大的影响。关于DDD的实践,与团队的水平、业务复杂度息息相关。我们的经验并不一定就适用你们团队。我将战术篇的这么多的内容放在了一篇文章中,并且大部分都是引用之前的讨论、总结。
原因还是在于我内心深处并没有觉得战术篇的实践给我们团队带来多么大的改变。战略篇的是我认为更重要的。

DDD系列文章断断续续也有十来篇了,主要是总结我们团队落地过程遇到的问题和解决方案,算是DDD从学习到落地实践的一个完整的闭环链路,希望对你有所启发。当然这个过程受益最大的肯定是我本人。系统性的思考问题、总结问题、阐述问题是非常有助于提升个人思维能力,朋友们你们也可以尝试一下。

建模

DDD的出现,是大家对于事务性编程,面向数据库表编程的一个反思,明明软件设计是一个面向对象的设计,需要考虑对象之间的继承、多态、组合。
为什么到实际编码过程中成了过程性的编程,为什么对象只有属性没有方法了,也就是失血模型。

关于这几种编程的详细介绍可以参考Martin的《Patterns of Enterprise Application Architecture》Page110

所以我个人觉得,DDD的作用有两个,一个是面向业务的,帮助分析业务模型,进行业务建模。另外一个是面向解决域,即代码落地。
即使用一个规范能够反映对象之间的关系,即OO编程。

目前对DDD研究主要有以下类别

  1. 关于业务分析层面,如何进行概念层面的抽象和设计的方法论
  2. 关于服务划分、代码分层、职责定义的方法论
  3. DDD框架的讨论,比如jdon

第3点基本上没怎么广泛的讨论。我认为未来也不会出现什么牛逼的DDD框架能够流行起来。DDD是一种建模方法,是针对不同的业务领域的,
在不同的团队有不同的落地方案,是没办法靠一种框架来约束,来把一件不统一的事情来统一起来。就好比我们面向对象的设计针对问题域,抽象出来了
20多种设计模式。这些设计模式都是指导思想,你不能搞出一种框架,来约束大家使用某种设计模式就基于这种框架扩展,以此来达到代码统一或者降低
编程难度的目的。

前面的文章主要是比较大的方面,比较适合做整体业务分析。也就是第一个点。今天主要讨论第二点。

OO 编程

DDD的代码分层、职责定义本质上就是OO编程。OO的三大基本要素就是继承、多态、组合。这三个是深度抽象的结果。没法指导具体的编程。
于是我们有了设计模式,前辈们针对问题域,总结除了24种设计模式,这样遇到类似的问题时,我们可以使用对应的设计模式去解决问题。
而这些设计模式底层使用到还是继承、多态、组合。

那有了设计模式了,为什么还要DDD呢?为什么很少看到开源软件用DDD呢?
个人的理解DDD还是面向企业应用架构的,是在众多不确定的业务,系统中提炼出来的一套规范,这样必然是高度抽象的。而开源软件大多是领域比较确定的,比如数据库领域,中间件领域。解决这类问题的系统架构通常会更加复杂以及具有扩展性。

DDD的工程架构网上有很多,我在之前的文章也提到过,这里不再赘述,看下老马的这个,我觉得非常清晰的展现出来了职责分离
https://martinfowler.com/articles/microservice-testing/#conclusion-summary
技术图片
我们重点看领域一层。
领域包含3点

领域服务

领域对象与领域服务

领域对象

敢于聚合根的激烈讨论

领域事件

CQRS能解什么问题

基础设施层

为各层提供资源服务(如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务逻辑的影响。

总结

以上是关于关于 DDD 对前端的指导工作的主要内容,如果未能解决你的问题,请参考以下文章

DDD专栏11微服务时代,单体架构淘汰了吗?

DDD专栏7:DDD如何指导微服务设计实现

领域驱动实战-支付系统

面试官:DDD如何指导微服务拆分?90%的程序员都答不上来!

在分布式系统中使用 DDD

前端DDD总结与思考