DDD领域建模被各个大小厂商提起并应用,而每个人都有自己的理解,本文就是针对小白,系统地讲解DDD到底是什么,解决了什么问题,及一些建议和实践。本文主要是思想的一种碰撞和分享,希望能对朋友们有所启发或帮助。
一、前言在当时的环境下,单体应用仍然是市场的主体,但是大型复杂软件系统已经出现,给团队的设计和开发工作带来了比较大的挑战。
DDD提供了一种新的设计思路,通过对于业务子域和限界上下文的划分,建立跨越业务和技术的统一语言,为业务建模的同时,拉通业务和技术实现。
DDD理论的提出,对整个软件架构设计领域,尤其是对微服务架构的设计产生了巨大的影响。那如何运用DDD来解决所面临的大型业务系统问题呢?
在这里以中台业务为例,进行实践和应用。
友情提示:看目录,从整体中深入内部去看
二、现状2.1 软件设计所面临的挑战无法回避这种复杂性,所做的只有控制这种复杂性。20多年前,顶尖的软件设计人员已经意识到领域建模和设计的重要性。尽管没有被清楚的表述出来,在对象社区涌动着一种新的思潮,Eric Evans把它称为领域驱动设计。领域驱动设计是一种思维方式,也是一组优先任务,它旨在加速那些必须处理理复杂领域的软件项目的开发。
初衷及目标:高内聚,低耦合
2.3 和传统架构设计相比DDD解决的问题DDD要解决的两个核心问题:
1.业务架构如何合理的设计?
2.如何使系统架构域业务架构保持一致并具备可持续的扩展?
领域驱动的设计基本目标:有效建模,并运用领域模型驱动团队进行沟通分析、设计及开发。
2.4 领域驱动设计的核心思想1、模型与现实业务的统一模型是浓缩的知识。模型是团队一致认同的领域知识的组织方式和重要元素的区分方式。透过我们如何选择术语、分解概念以及将概念联系起来,模型纪录了我们看待领域的方式。
当开发人员和领域专家在将信息组织为模型时,这一共同的语言(模型)能够促使他们高效地协作。
4、领域驱动设计三原则P1: Focus on your core domain.
※ 聚焦于你的 核心领域
P2: Iteratively explore models in a creative collaboration of domain practitioners and software practitioners.
※ 通过协作 迭代 探索模型
P3: Speak a Ubiquitous Language within an explicitly Bounded Context.
※ 统一语言
2.5 领域驱动的设计步骤5.1 统一语言同一对象在不同上下文中的概念可能是不同的。
领域太复杂,只有在分割的上下文内才可能形成统一语言。
在UML作为建模主流的时代,软件设计被明确分为面向对象分析(OOA),面向对象设计(OOD)和面向对象编码(OOP)阶段。
实际操作中OOD的工作往往被OOA和OOP各自承担一部分,并同时存在分析模型和设计模型两个割裂的模型。
而领域驱动设计的核心是建立统一的领域模型。领域模型在软件架构中处于核心地位,软件开发过程中,必须以建立领域模型为中心,以保障领域模型的忠实体现。
简单理解起来的话,也就是把业务人员和开发人员的语言统一起来,用代码来感受一下大概就是:
userService.love(Jack, Rose) => Jack.love(Rose)companyService.hire(company,employee) => Company.hire(employee)
5.2 领域内的事件风暴、命令风暴
领域建模的分析过程及方法:
5.2.1 什么是领域事件?- 捕获我们所建模的领域中所发生过的事情
“任何的业务事件都会以某种数据的形式留下足迹。我们对于事件的追溯可以通过对数据的追溯来完成。......你无法回到从前去看看到底发生了什么,但是却可以在单据的基础上,一定程度的还原当时事情发生的场景。当我们把这些数据的足迹按照时间顺序排列起来,我们几乎可以清晰地推测出这个在过往的⼀段时间内到底发生了哪些事情 ”
要点:
1、业务关心的事件 :一个领域事件必须对业务有价值,有助于形成完整的业务闭环,也即一个领域事件将导致进一步的业务操作。
2、用“已发生”时态描述
3、有时间顺序
5.2.2 如何进行命令风暴?围绕第一步识别出的领域事件, 针对各个事件进行命令分析。什么样的命令导致的这个事件的发生。
有的命令可能产生多个事件,可以使用箭头将它们连接
5.2.3 寻找聚合什么是聚合在领域驱动设计中,聚合是一组相关领域对象,其目的是要确保业务规则在领域对象的各个生命周期都得以执行:
‣ 聚合边界内保证业务不变性(invariant)
‣ 只能通过聚合根修改边界内的对象
‣ 聚合根有全局标识
5.2.4 持续探索:
领域驱动开发强调维护应用程序模型的概念完整性。但如果不对模型的概念完整性 进行妥协的话,传统的DDD方法不能盲目地应用在⼀个无限大的领域模型中。
真正让模型得以最大程度地扩展,并且不必牺牲其概念完整性的方法叫做“上下文”。限界上下文明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的⼀致性,而不要受到边界之外问题的干扰和混淆。
通过定义不同上下文之间的关系,创建一个所有模型上下文的全局视图。
2.6 DDD领域设计的服务划分
6.1 修缮模式(遗留系统重构)“修缮者模式”在既有系统资产的基础上,通过剥离新业务和功能,逐步“释放”现有系统耦合度,解决遗留系统质量不稳定和Bug多的问题。实现传统IT性能提升,面对传统的IT业务更加稳定灵活,降低维护成本。
‣修缮模式适用于需求变更频率不高的存量系统
6.2绞*模式(遗留系统重构)绞*者模式”在既有系统资产的基础上实现数字IT创新,面对创新的数字IT业务更加灵活。通过在新的应用中实现新特性,保持和现有系统的松耦合,仅在必要时将功能从原系统中剥离,以此逐步地替换原有系统。
6.3 服务划分原则依据业务限界上下文边界划分‣在业务层面实现了高内聚和低耦合‣ 在颗粒度上是比较适应刚开始做服务化架构的团队能力的
‣按照组织结构划分‣ 依照康威定律来划分服务
‣对于技术能力相对比较成熟的团队,可以依据识别出来的聚合关系拆分服务
在实操过程中,不同组织也在应用以下服务划分原则‣按照变更的频率拆分‣ 可以按照系统功能中不同的变更频率来划分服务,通常来说,越靠近终端用户的特性变更频率越高,反之越低
‣按照技术异构的边界划分‣ 按照不同的技术选型发展方向来划分系统
‣按照业务对于伸缩性或者健壮性的不同要求划分‣ 按照企业对于安全性的不同要求划分
2.7 DDD全景图
1、战略设计战略设计会控制和分解战术设计的边界与粒度,战术设计则以实证角度验证领域模型的有效性、完整性与一致性,进而以演进的方式对之前的战略设计阶段进行迭代,从而形成一种螺旋式上升的迭代设计过程,如下图所示:
2、战术设计3、全景视图操作流程两个不同阶段的设计目标是保持一致的,它们是一个连贯的过程,彼此之间又相互指导与规范,并最终保证一个有效的领域模型和一个富有表达力的实现同时演进。
2.8 领域模型
DDD革命性在于:领域模型准确反映了业务语言,而传统J2EE或Spring Hibernate等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被比喻成失血模型。
那模型都有几种,他们的区别是什么?我们该怎么选择? (题外话:各有优劣,大家需要找到自己期望的平衡点,不要盲目的使用任何一种。)
1、模型分类及特征总结一下的话就是,基于贫血模型的传统的开发模式,重 Service 轻 BO;基于充血模型的 DDD 开发模式,轻 Service 重 Domain。
这种模型的优点:1、各层单向依赖,结构清楚,易于实现和维护 2、设计简单易行,底层模型非常稳定。这种模型的缺点:1、domain object的部分比较紧密依赖的持久化domain logic被分离到Service层,显得不够OO 2、Service层过于厚重。
这种模型的优点:1、更加符合OO的原则 2、Service层很薄,只充当Facade的角色,不和DAO打交道。这种模型的缺点:1、DAO和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。2、如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序 3、考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且Service的事务化封装其意义就等于把OO的domain logic转换为过程的Service TransactionScript。该充血模型辛辛苦苦在domain层实现的OO在Service层又变成了过程式。
对于Web层程序员的角度来看,和贫血模型没有什么区别了。
1.事务是不希望由Item管理的,而是由容器或更高一层的业务类来管理。
2.如果Item不脱离持久层的管理,如JDO的pm,那么itemDao.update(this); 是不需要的,也就是说Item是在事务过程中从数据库拿出来的,并且声明周期不超出当前事务的范围。
3.如果Item是脱离持久层,也就是在Item的生命周期超出了事务的范围,那就要必须显示调用update或attach之类的持久化方法的,这种时候就应该是按robbin所说的第2种模型来做。
该模型优点:1、简化了分层 2、也算符合OO 。该模型缺点:1、很多不是domain logic的service逻辑也被强行放入domain object ,引起了domain ojbect模型的不稳定 2、domain object暴露给web层过多的信息,可能引起意想不到的副作用。
评价:在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来说,都已经是可行的了。
第一种模型绝大多数人都反对,因此反对理由也便不多讲了。但遗憾的是,观察到的实际情形是,很多使用Hibernate的公司最后都是这种模型。这里面有很大的原因是很多公司的技术水平没有达到这种层次,所以导致了这种贫血模型的出现。从这一点来说,Martin Fowler的批评声音不是太响了,而是太弱了,还需要再继续呐喊。
第二种模型就是Martin Fowler一直主张的模型,实际上也是一直在实际项目中采用这种模型。不过觉得这种模型仍然不够完美,因为你还是需要一个业务逻辑层来封装所有的domain logic,这显得非常啰嗦,并且业务逻辑对象的接口也不够稳定。如果不考虑业务逻辑对象的重用性的话(业务逻辑对象的可重用性也不可能好),很多人干脆就去掉了xxxManager这一层,在Web层的Action代码直接调用xxxDao,同时容器事务管理配置到Action这一层上来。Hibernate的caveatemptor就是这样架构的一个典型应用。
第三种模型是很反对的一种模型,这种模型下面,Domain Object和DAO形成了双向依赖关系,无法脱离框架测试,并且业务逻辑层的服务也和持久层对象的状态耦合到了一起,会造成程序的高度的复杂性,很差的灵活性和糟糕的可维护性。也许将来技术进步导致的O/R Mapping管理下的domain object发展到足够的动态持久透明化的话,这种模型才会成为一个理想的选择。
就像O/R Mapping的流行使得第二种模型成为了可能Martin Fowler的Domain Model,或者说我们的第二种模型难道是完美无缺的吗?当然不是,接下来就要分析一下它的不足,以及可能的解决办法。
(详细代码示例及解析请参阅:https://my.oschina.net/u/1999167/blog/1841997 )
3、为什么基于贫血模型的传统开发模式如此受欢迎?前面讲过,基于贫血模型的传统开发模式,将数据与业务逻辑分离,违反了 OOP 的封装特性,实际上是一种面向过程的编程风格。
但是,现在几乎所有的 Web 项目,都是基于这种贫血模型的开发模式,甚至连 Java Spring 框架的官方 demo,都是按照这种开发模式来编写的。
4、 DDD 完全的充血或者胀血模型得与失在聊到DDD的时候,经常会做一个假设:假设你的机器内存无限大,永远不宕机,在这个前提假设下,是不需要持久化数据的,也就是可以不需要数据库,
那么你将会怎么设计你的软件?这就是Persistence Ignorance:持久化无关设计。没了数据库,领域模型就要基于程序本身来设计了,热爱设计模式的同学们可以在这里大显身手。在面向过程,面向函数,面向对象的编程语言中,面向对象无疑是领域建模最佳方式。
类与表有点像(不少人认为表和类就是对应的,行row和对象object就是对应的),个人强烈地不认同这种等同关系,这种认知直接导致了软件设计变得没有意义。类和表有以下几个显著区别,这些区别对领域建模的表达丰富度有显著的差别,有了封装、继承、多态,对领域模型的表达要生动得多,对SOLID原则的遵守也会严谨很多。
由于不再承载领域建模这个特性,数据库的设计可以变得天马行空,任何可以加速存储和搜索的手段都可以用上,可以用column数据库,可以用document数据库,可以设计非常精巧的中间表去完成大数据的查询。总之数据库设计要做的事情就是尽可能地高效存取,而不是完美表达领域模型,这样再看看架构图:
所以:理想很丰满,现实很骨感。并不能有无限大的内存。 在实际应用中一定是要避免模型的胀血。不然会带来很大的麻烦。
2.9 领域驱动在中台业务分析中的实践
1、业务中台DDD领域 应用架构规范2、业务中台使用DDD领域建模的愿景(架构分层)3、现有系统使用DDD进行领域分析4、数据结构模型边界的构建
个人总结:DDD只是一种方法论,网上各种公司的DDD框架,也只是对方法论按照自己的理解进行的一种实践和实验。所以不要纠结于什么才是一个DDD框架,怎么写一个DDD框架。
更不要死板的去套DDD的各种充血贫血模型,找到适合自己的模型,解决建模问题才是最重要的。DDD只是为你提供了一套系统的方法论仅此而已,并没有多高大上。
三、推荐教程与文档板桥大话DDD(https://www.jdon.com/ddd/ddd-domain.html)用大白话简单谈谈DDD的一些基础特点,只是扫盲!数据库SQL强人慎入
板桥DDD研究十年心得:《复杂软件设计之道:领域驱动设计全面解析与实战》(https://www.jdon.com/54881.html)承蒙机械出版社厚爱
板桥:为什么DDD的Bounded Context翻译为"有界上下文"?(https://www.jdon.com/55608.html)
板桥:DDD中问题空间和解决方案空间是一个伪命题(https://www.jdon.com/55682.html)
业务代码编程陷阱案例 - jaxenter(https://www.jdon.com/53806.html)非常普遍的不恰当的编程方式,失血模型导致的陷阱
面向对象建模与数据库建模两种分析设计方法的比较(https://www.jdon.com/mda/oo_relation.html)数据库驱动设计与对象建模是决定软件不同命运的两大派别,谁可以让软件更具有生命,维护拓展更方便?伸缩性更强?
鲍勃大爷:先设计对象的行为,再设计数据库表结构!(https://www.jdon.com/55056.html)软件工程大师、敏捷创建者之一鲍勃大叔的建议
面向对象与领域建模(https://www.jdon.com/mda/modeling.html)据调查,目前有70%左右程序员是在使用OO语言编写传统过程化软件,缺乏完整的面向对象思维方法的教育和培训是基本根源,本文对软件开发中几个常见问题提出了独立的见解及尖锐的观点
Evans DDD 领域建模(https://www.jdon.com/mda/dddcase2.html)如何提炼模型,而不是数据表,进而精化模型对象,使其能够反映领域概念基本本质是一个复杂过程,Evans DDD是2004年提出的具备革命性影响的软件思想。
实战DDD(Evans DDD:Domain-Driven Design领域驱动设计)(https://www.jdon.com/mda/ddd.html)领域建模是一种艺术的技术,不是数学的技术,它是用来解决复杂软件快速应付变化的解决之道。
领域模型驱动设计(Evans DDD)之模型提炼(https://www.jdon.com/mda/dddcase2.html)
软件建模设计(https://www.jdon.com/mda/communicating-design.html)
如何从职责和协作中发现丰富的充血对象?失血模型贫血模型是DDD最大敌人,如何根据SOLID原则(https://www.jdon.com/tags/39825)和GRASP原则(https://www.jdon.com/tags/44849)设计业务行为?本文给出了DDD具体实践中一些具体细节,是和DDD配合一起进行面向对象分析设计的好方法。
业务模型统一描述(https://www.jdon.com/44416.html)统一语言是DDD一个重要特征和重点。
DDD CQRS和Event Sourcing的案例:足球比赛
DDD CQRS Event Sourcing实现案例,结合代码与理论讲解。
集装箱车队系统的DDD案例
为上海某大型港口公司的运输系统实施的一个领域驱动设计DDD的实战咨询案例。
DDD仓储实现:Spring Data JDBC教程(https://www.jdon.com/springboot/spring-data-jdbc.html)
不使用DDD的后果:为什么我们停止了向微服务的迁移?(https://www.jdon.com/52922.html)
使用DDD聚合发现隐藏的业务规则的案例分析:数据库事务的业务实现 (https://www.jdon.com/53449.html)
向领域驱动设计前进:如何使用DDD实现从单体到微服务迁移打造业务平台或中台?(https://www.jdon.com/53462.html)
DDD 微服务大型案例:Uber如何从复杂的RPC微服务转向面向业务领域的微服务架构DOMA?(https://www.jdon.com/54664.html)
全球大型电商Shopify如何使用DDD实现单体架构的模块化?(https://www.jdon.com/55003.html)
更多#DDD领域驱动设计专题、领域事件专题(https://www.jdon.com/tags/272)
四、总结理论是死的,并不代表一定要完完全全照着这种思想去实现代码;比如贫血、充血模型。他们都有自己擅长的地方,DDD的核心主要还是如何把一个复杂的巨型系统解耦,拆解成合理的微服务。要理解DDD的思想,但不要死搬硬套。
参考博客文献:(吃水不忘挖井人)作者:京东零售 黄学斌
来源:京东云开发者社区
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved