DDD
至少30年以前,一些软件设计人员就已经意识到领域建模和设计的重要性,DDD这一名词,由埃里克·埃文斯(Eric Evans)在2003发表的《领域驱动设计》一书提出。这本书理论性极强,奠定了领域驱动设计这一综合性软件设计理论的基础。书籍本身也成为DDD的“圣经”。截止作者出搞该书发表已有20年。
笔者08年进入互联网行业,13年第一次听说DDD,到最终落地2020年经历了至少8年的时间。
回顾过往的经历也不难发现,在互联网发展的早期,业务相对简单,在“小步快跑,迭代试错”的大环境下,DDD 反倒是一种“古老而缓慢”的思想。
在国内互联网高速发展的近十年来,深刻体会到C端消费互联网的高速发展推动着技术的成熟,同时对传统产业不断深入重构,随之带来的业务的日益复杂。DDD的概念逐惭回到大家的视野。
DDD 不是新的技术,反而是古老的,DDD其实是让我们更准确的使用面向对象
。反观我们走过来的这十几年,其实是在用面向对象的语言,在写面向过程的代码。
复杂性的挑战
Eric Evans)在2003发表的《领域驱动设计》一书副标题是“软件核心复杂性应对之道”
在软件落地过程有很多因素会使软件开发复杂化,但最根本的原因是问题领域本身的错综复杂。但我们无法回避这种复杂,只能控制这种复杂。
很多因素可能导致项目偏离轨道,如官僚主义、目标不清、资源不足等。但真正决定软件复杂性的是设计方法
。很多应用的复杂性并不在技术上,而是来自领域本身,用户的活动或业务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也无济于事。成功的设计必须系统地考虑这个核心方面。
所以综上:DDD并不是银弹,使用DDD的前提是我们的业务足够复杂,脱离业务谈DDD,都是不讲武德的!
设计过程与开发过程
敏捷是一种项目管理和软件开发的迭代方法,帮助团队更快、更好地向客户提供价值。
敏捷,关注流程和文化
,DDD关注建模设计方法
;敏捷,重人员轻文档,DDD重视统一语言的共识
。
DDD是一种设计过程,一般和极限编程结合落地。
面向“敏捷开发过程”这一新的体系。我们假定项目遵循两个开发实践
- 迭代开发
- 开发人员必须与领域专家具有密切的关系。
极限编程
[一种敏捷开发过程]承认设计决策
的重要性,但强烈反对预先设计。相反,它将相当大的精力投入到促进沟通和提高快速变更能力
的工作中。具有这种反应能力之后,开发人员就可以在项目的任何阶段只利用“最简单而管用的方案”,然后不断进行重构,一步一步做出小的设计改进,最终得到满足客户真正需求的设计。
敏捷宣言
imagehttp://www.extremeprogramming.cn/content/xp/test-first.html
image imagehttps://www.inflectra.com/Ideas/Topic/User-Stories.aspx
image运用领域模型
领域
模型
被用来描绘人们所关注的现实或想法的某个方面。模型是一种简化。它是对现实的解释
和抽象
。
它与解决问题密切相关的方面的抽象相关,而忽略无关的细节。
每个软件程序是为了执行用户的某项活动,或是满足用户的某种需求。这些用户应用软件的问题区域就是软件的领域
。一些领域涉及物质世界,有些领域则是无形的。
为了创建真正能为用户活动所用的软件,开发团队必须运用一整套与这些活动有关的知识体系
。所需的知识的广度可能令人望而生畏,庞大而复杂的信息也可能超乎想象。模型正是解决此类信息超载问题的工具。模型这种知识形式对知识进行了选择性的简化
和有意的结构化
。适当的模型可以使人理解信息的意义,并专注于问题
。
模型在领域设计中的作用
在DDD 中三个基本的用户决定了模型的选择:
- 模型的设计的核心互相影响。模型与实现之间的紧密联系才使模型变得有用:
- 确保我们在模型中所进行的分析能够转化为代码。
- 在后期维护期间可以基于对模型的理解来解释代码。
- 模型是团队所有成员使用的通用语言中枢(
共识的结果
),技术团队与领域专家无翻译沟通。 - 模型是浓缩的
知识
,- 来自软件早期的经验可以作为反馈应用到建模过程中(迭代)
软件的核心
软件的核心是其为用户解决领域相关问题的能力。所有其他特性,不管有多么重要,都要服务于这个基本目的。(大部分开发人员只专注于技术本身不关心业务知识,只见树木,不见森林)
有效建模的要素
- 模型和实现的绑定
- 建立了一种模型的语言
- 开发一个蕴含丰富知识的模型
- 提供模型
在模型日益完善的过程中,重要的概念
不断被添加到模型中,但同样重要的是,不再使用的或不重要的概念则从模型中被移除。当一个不需要的概念
与一个需要的概念有关联时,则把重要的概念提取到一个新模型
中,
知识消化
知识消息不是一项孤立的活动 ,它一般是在开发人员的领导下,由开发人员与领域专家组成的团队来完成的。他们共同收集信息,并通过消化而将知识组织为有用的形式。信息的原始资源来自于:
- 领域专家头脑里的知识
- 现有系统的用户
- 技术团队以前在相关遗留系统或同领域的其他项目中积累的经验。
信息的形式也多种多样
传统的瀑布流方式因为没有反馈
,所以项目经验失败
。知识只是朝一个方向流量,而且不会累积。
模型在不断改时的同时,也成为组织项目信息流(统一能用语言)的工具。模型聚集于需求分析
。 它与编程和设计紧密交互。它通过良性循环加深团队成员对领域的理解,使他们更透彻地理解模型,并对其进一步精化。模型永远都不不会是完美的
,因为它是一个不断演化完善的过程。模型对理解领域必须是切实可用的。它们必须非常精确,以便使应用程序易于实现和理解。
SDLC (System Development Life Cycle)系统开发生命周期
image imageWaterffull vs agile
领域模型并不是按照“选建模,后实现”这个次序来工作的。象很多人一样,我也反对“先设计、再构建”这种固化的思维模式。Eric的经验告诉我们,真正强大的领域模型是随着时间演进的,即使是最有经验的建模人员也往往发现他们是在系统初始版本完成之后才了最好的想法。
Martin fowler
imagehttp://www.agilenutshell.com/agile_vs_waterfall
DDD与架构
架构这个词源于英文里的“Architecture“,源头是土木工程里的“建筑”和“结构”,而架构里的”架“同时又包含了”架子“(scaffolding)的含义,意指能快速搭建起来的固定结构。而今天的应用架构,意指软件系统中固定不变的代码结构、设计模式、规范
和组件
间的通信方式。在应用开发中架构之所以是最重要的第一步,因为一个好的架构能让系统安全、稳定、快速迭代
。在一个团队内通过规定一个固定的架构设计,可以让团队内能力参差不齐的同学们都能有一个统一的开发规范,降低沟通成本,提升效率和代码质量。
DDD不是一种架构思想。DDD和架构之间是正交的关系(就是没啥关系
),DDD可以和多种架构方式配合使用,包括n层架构,端口和连接器架构,Clean架构,微服务架构等等。DDD是一种设计范式
,主张以领域模型为中心驱动整个软件的设计。在DDD中,业务分析
和领域建模
是软件开发的关键活动。它不关心软件的架构是怎样的。随着技术的发展,我们可能在新版本中更换软件的架构,但是只要业务没有变更,领域模型就是稳定的,无需改动。笔者的开源小项目,已经实现了对基础设施的动态替换,spring 都可以换
Robert C. Martin 在《整洁架构》一书中认为,架构
和业务功能无关
,只聚焦于软件质量
,尤其是内部质量。而DDD完全聚焦于业务功能
——发现问题域的内部组成、结构、规则和机制。
在做架构设计时,一个好的架构应该需要实现以下几个目标:
-
独立于框架:架构不应该依赖某个外部的库或框架,不应该被框架的结构所束缚。
-
独立于UI:前台展示的样式可能会随时发生变化(今天可能是网页、明天可能变成console、后天是独立app),但是底层架构不应该随之而变化。
-
独立于底层数据源:无论今天你用MySQL、Oracle还是MongoDB、CouchDB,甚至使用文件系统,软件架构不应该因为不同的底层数据储存方式而产生巨大改变。
-
独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应该随之而大幅变化。
-
可测试:无论外部依赖了什么数据库、硬件、UI或者服务,业务的逻辑应该都能够快速被验证正确性。
策略模式与充血模型设计?
传统系统设计,大部分从数据库开始--自底向上的设计,这种设计会使系统的设计受到数据库
的影响,会有比较大的局限性,比如说:数据库仅有数据,没有行为
,而对现实世界的描述则会更加抽象,更加远离业务.开发团队通过与产品或客户的沟通,直接设计表模型,由于会受到沟通的效率
的影响,客户不懂技术,开发从技术角度思考,整个沟通过程则会有较大的信息损失(失真)
,所设计出的表模型则很可能面临后期业务变动的挑战.导致系统的成败主要取决于架构师在那个领域的业务水平而不是技术水平.
而DDD是采用自顶而下的设计,战略设计时业务为王,不再受到技术的局限性(笔者认为作设计的时侯应该把技术忘掉
),设计过程中,需要业务专家(不用懂技术,比如客户和产品等)和开发团队一块仅仅从业务的角度分析需求,不要受到任何技术的局限性,采用事件风暴(类似头脑风暴)的方式,讨论整个系统面临的各种用户场景,整个系统提供的各个功能模块,做业务上的归集分类分级,在讨论过程中,各个团队(客户 产品 开发 测试 运维 交付)逐渐统一语言(同一个限界上下文中),使得业务场景更加清晰的被描述和归纳分类出来,在整个沟通中更加有效率,更加准确,后续维护阶段的持续交付迭代都将由此而获益。其实就是共识
一套术语表。当然这个术语也是可以迭代的。
DDD整洁架构实践
https://github.com/Sairyss/domain-driven-hexagon
六边形架构与洋葱架构其中心思想类似
image纵向DGA架构设计
DDD六边形分层实践关于DDD战术设计的一些细节问题
存储库是为聚合而服务的
存储库不是一个对象。它是一个程序边界以及一个明确的约定,在其上命名方法时它需要的工作量与领域模型中的对象所需的工作量一样多。你的存储库约定应该是特定的以及能够揭示意图并对领域专家具有意义。
image.png笔者与淘系技术观点不一致, repository 面向聚合服务是可以共识的,但domain service 不允许调用repository 并没有严格规定,原则上是可以调用的。包括 Value Object 中也是允许调用domain service 的。
https://cloud.tencent.com/developer/article/1861060
https://cloud.tencent.com/developer/article/1803939
我们是面向VO建模还是面向 Entity 建模?
尽量避免public setter
IDDD 的作者费农建议面向 VO建模,原因是VO是只读的,对于复杂业务场景下不会被修改,对于上下游的业务逻辑是安全的,笔者非常认同。因为DDD的核心目标就是在解决复杂性,而复杂性的根本原因在于团队协作与沟通共识。尤其在某一个业务场景下,多人开发时侯,可能会莫名的对下游的逻辑产生影响。而面向VO建模可以避免类似问题的产生。同时也是对代码和团队成员的限制和规范。
而相反ENTITY 是允许修改的,比如订单的状态,很有可能被上游队友(猪一样的队友还是有的
)无意修改,导致线上bug 异常。
聚合是否需要单独的类?
实践的过程中我么是否需要为聚合建立一个单独的类呢?答案是不需要。聚合只是一个虚拟的边界,它并不是一个单独的类。在实际中只需要用聚合根实体来表示这个聚合就可以了。不需要单独的聚合类。
领域服务是否需要定义成接口?
大多数情况下不需要,但在领域服务多种实现,或者有可能有多种实现的情况,需要定义接口,并在基础设施层提供实现。iddd p244
什么时侯需要定义领域服务?
执行一个显著的业务操作过程。比如密码加密(支持多种加密方式)
对领域对象进行转换
以多个领域对象(aggrgation,entity,value object) 作为输入进行计算,结果产生一个值对象。
领域对象的依赖方法
Domain Registry(服务工厂)
依赖注入(依赖spring)
构造器(或直接参数传递)
以上三种方式并无好坏倾向,网上多数情况使用第三种(IDDD p246)
网友评论