DDD凝聚了软件工程的智慧
许多人对微服务设计中经常提及的DDD非常推崇,觉得这是最新的架构设计趋势和解决微服务业务划分的终极方法。实际上,DDD概念最早在2004年就提出来了,微服务的前身SOA的概念也是在那个时候被提出的。DDD的思想凝聚了软件工程师在面向对象和服务架构开发中的很多宝贵思想。本文试图解构DDD背后的思想。
DDD领域驱动设计的历史
DDD根植于面向对象(OOP)思想以及面向服务架构(SOA)的影响。最早期的面向对象概念是Alan Key在1966年左右构思出来的。最早是通过像prototype这样的原型重用的方式获得一些功能模块的继承和封装的特性。面向对象编程对于处理越来越复杂的软件逻辑和组织软件工程项目起到了非常重要的作用,并逐渐被软件开发人员所接受。目前普遍认为第一个OOP编程语言是挪威计算机科学家Ole-Johan Dahl和Kristen Nygaard开发的Simula。
Eric Evans是DDD的提出者。在2003年的时候,他写作了名为《领域驱动设计:处理软件核心的复杂性》这本书。在这本书里,他首次提出并介绍了领域驱动设计也就是DDD的相关方法论。他认为DDD代表了一种新型的,更加成熟的软件开发方法,是面向对象分析和设计(OOAD)的进化。
什么是领域 Domain
在软件开发中的领域指的是软件应用开发过程中涉及的知识和活动范围。在软件设计中更加通用的说法是领域层和领域逻辑,这两个概念更加常用的近义词则是业务层和业务逻辑。
对于领域驱动的基础和本质还是面向对象方法。我们首先要能够将业务逻辑涉及的概念合理拆解成对象。这是软件工程人员的基本功,如果连这一点还不能做好那其实就还没有入门现代软件设计思想。举个简单的业务场景。银行的一个储户要在银行的账户中取一笔钱,但是如果这笔钱的数额超过了他账户中的金额,那么系统需要阻止这笔交易,并发送提示或者视情况锁定账户一段时间。在这个业务场景中,我们应该定义哪些对象(Object)?大家应该很容易想到,1.储户;2.账户。这个场景中储户对象可以调用账户对象的取款方法,如果余额不足,方法返回提示。在设计中如果错误地将储户和账户合并在一起作为一个对象会如何?在这样简单的情况下不会有什么大问题。但是肯定会造成数据冗余,比如如果一个储户有两个账户,那记录两条账户的信息必须也包含储户信息,而且储户信息都是重复的。这样的错误设计问题在系统中长期积累就会成为技术负债,增加系统的维护和二次开发的难度,增加系统出性能和逻辑出问题的风险。
领域驱动设计
随着时间的推移,软件开发领域经历了很多变革。DDD的提出者Eric Evans对于领域驱动设计的定义也发生了变化。目前他本人比较偏向的DDD定义也已经不同于2004年出版的第一版图书中的定义内容了。Evans对DDD的定义有好多种描述。这些描述其实也是从不同的侧面来解释这个概念。他认为,定义DDD最好的方式之一是将其设定为一系列驱动原则(看来大佬都喜欢提出原则,桥水的瑞.达利欧出了一本叫《原则》的畅销书)。这些原则包括如下几点:
- 关注核心领域-”人们总是会被技术细节分心,我们应该将他们的注意力拉回业务领域“,Evans说。即使整个业务领域有太多东西需要关注,也应该如此。事实上,DDD需要我们将注意力集中到最核心,最重要和最有价值的部分。
- 我们应该同领域的实践者以及软件开发者以创新合作的方式探索软件的模型。“我们应该和业务专家一起合作,而不是仅仅咨询和提问”。
- 使用统一和有边界的描述语言进行交流和讨论,这个应该指的是软件设计人员和业务需求方专家应该有一种能够交流并且双方都能理解的业务流程和思想的描述模型,比如UML(统一建模语言)。
领域驱动设计几大原则详解
-
聚焦核心领域:对复杂的业务领域进行建模,区分不同模型边界以及对模型进行构建有非常多的方面需要考虑。
首先,对于采用DDD模式的项目,我们必须聚焦于业务领域,也就是我们希望通过功能模块或者业务模型试图去解决的业务问题。而不是一开始就在技术层面上过多分心,比如考虑使用什么开发工具,开发框架等,这些问题对于技术团队非常重要,但是在领域驱动开发中不应是第一优先级的问题。
其次,我们需要聚焦于目前要解决的业务领域,而不要扩散范围。比如在银行系统中有储蓄,借贷,信用卡,风控等不同的子领域。当我们定义出的业务需求和业务模型的领域是在储蓄时,就不应该将注意力过多分散到其他的领域中去。
最后,需要提到的是,在整个软件系统中,并不是所有的领域都有相同的重要性。同时,在不同的企业和组织中,相同业务领域的重要性,关注点和复杂度也是完全不同的。在这个问题的解释上,Evans举了亚马逊和易贝的区别,对于网上售书这个业务领域,由于这两家的业务模式不同(B2C和C2C)。亚马逊关注的是买家的评价,而相对来讲Ebay会更加关注卖家的感受和需求。
-
在创新合作中探索业务模型:创造出真正反映现实业务的模型,是业务软件项目设计成功的关键。这些模型并不是完全复制现实世界中的东西,而是要抓住本质性的业务逻辑。和领域专家一起,认真思考领域中真正重要的概念和信息,而不是无限深挖细节。比如说金融类的项目,如果是一个贷款流程,是否需要对流程中的人进行分析和记录,这个需要根据当时的实际情况。但是如果设计到审计和评估,那么这个过程中的每个业务人员的信息则非常重要。迭代在模型探索中也非常重要,现实中的环境和业务是在不断变化的,而且我们对于某个业务模型的认识和思考也是不断改进和调整的。因此这些改变和调整应该以迭代的形式不断填补和融合进领域模型中去。
-
使用统一描述语言:我们知道英语是现代国际交流的通用语言(Lingua Franca),两个来自不同母语国家的人要进行交流,通常都会选择使用英语进行交流。这是因为英语是国际间能够被主流认可,并且受过高等教育的人在一定程度上都能够理解和使用的语言。在软件项目交流中,不同的团队和业务专家通常也需要有一个通用语言进行项目逻辑和需求的交流,这种通用描述可以是标准的也可以是组织内部逐渐形成的一种描述体系。在大型的企业和组织中往往都存在很多这样的内部人能够快速理解的名称和概念。在软件工程领域中,我们其实也有标准的描述语言,那就是UML统一建模语言。自然语言通常都有很多歧义和不明确性,统一描述语言必须要有清晰的语义结构,概念,流程的确定表述方式。
领域驱动模型的概念
领域驱动模型有很多专门的术语和概念,在使用DDD模式的时候,这些术语和概念非常重要。
- 上下文(Context),任何领域模型都必须是在其特定的上下文环境中,如果DDD超出了其界定的上下文,则会产生歧义或者错误。例如社交媒体中的连接和网络应用中的连接的含义是完全不同的。
- 上下文关联(Context map),对于任何上下文,我们都必须要能够建立明确的上下文的关联关系和边界。模型中的角色和边界的定义,在项目中非常重要。
- 领域(Domain),指的的是某个专业知识范围,这就是领域模型中领域的意思,是做大的这个知识集合,包含其中的各类上下文(Context)。
- 模型(Model),模型是用于描述该领域中的某个方面的工能,角色,特点和属性等信息的模板。模型用于研究和解决该领域内的问题。
- 有界上下文(Bounded Context),在DDD是一个扩充的概念,在Evans经典DDD的书里应该是没有这个概念的,Jdon论坛上面的很少涉及Bounded Context文章。有界上下文不同于Role(DCI)它们看起来有点相似,但有界上下文的描述范围更加广泛一点。Role(DCI)根据角色直接捕捉角色与系统交互行为,更接近于业务需求,有界上下文更强调则业务分析。
- 战略(Strategies)一个系统应该有尽可能少的部件来精确描述真实的世界,同时应该确保涵盖真实世界的所有主要相关部分,同时不包含任何额外的附加物。 战略设计是一系列的原则和规定,用于保持模型的完整性,细化领域模型,并维护多个模型。
- 实体(Entity)传统的OOP中,对象是由它们的属性,属性, 和方法构成的。 DDD中的实体则是通过其持续的一致性来标识的对象。
- 价值对象(Value object)值对象的定义是:描述事物的对象;更准确地说,一个没有概念上标识符描述一个领域方面的对象。这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性表示但同时表达了领域中某类含义的概念。通常值对象不具有唯一id,由对象的属性描述,可以用来传递参数或对实体进行补充描述。作为实体属性的描述时,值对象也会被存储。在uml的类图上显示为一对多或一对一的关系。在ORM映射关系上需要采用较复杂的一对多或一对一关系映射。
- 领域事件(Domain event)领域事件对象用于记录模型产生的离散事件,我们必须谨慎选择和记录领域事件,仅仅对于那些有意义的事件进行记录。
- 聚合(Aggregate)聚合是良好定了边界的值和对象的组的集合。每个聚合实体有一个聚合根代表整个聚合中的实体和值。这样外部对象如果要访问聚合中的实体和值,只需要访问聚合根实体就可以。
- 服务(Service)在某些情况下,最清楚、最实用的设计会包含一些特殊的操作和逻辑,这些操作和业务逻辑从概念上讲不属于任何对象,与其把它们强制归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是Service(服务)
- 库(Repositories)库指的是所有拥有共同实体和值接口的服务,且这些实体和值都在同一个聚合组中。库通常应该有增加,删除,修改和查找组中对象的方法。可以直接通过应用业务逻辑查询的引用来进行简洁的查询。
- 工厂(Factories)工厂用于封装用于创建复杂的对象和聚合的逻辑,对工厂使用者隐藏内部对象的操作关系。
- 持续集成,持续开发和持续测试(CI/CD/CT):这和DebOps的持续集成,持续开发,持续测试的概念基本一致。DDD强烈推荐使用CI、CD、CT的方法来实现。
领域驱动设计的挑战
DDD不是软件设计和架构的万能解决方案,其也有自己的适用范围和局限性。
- 必须要有相关的领域专业知识,如果在团队中没有该业务领域的专家,或者说领域专家不会深入参与到项目的日常推进和迭代中去,则实施DDD是很难以成功的。
- DDD模式的软件设计和过程非常依赖于迭代式的敏捷开发模式,需要团队进行频繁的沟通和改进。要求项目团队工作在敏捷开发模式下。如果是传统的瀑布模式的开发项目,通常很难有效利用到DDD的优点。
- DDD是为了解决实际复杂业务相关的软件类开发项目。例如金融机构的业务程序,大型企业的在线商务系统,城市交通管控系统等等。这些领域有非常多的专业领域知识,可以非常好地受益于DDD这样的模式进行模型建立和业务系统的开发,迭代和运维。但是对于纯粹的技术导向性项目,或者简单业务类项目是没有什么意义的。例如研发新的机器学习算法,开发新型的分布式数据库系统等。开发人员在这种强技术相关类项目中通常不需要重度依赖业务领域知识,也不需要业务专家的深度参与。大部分的开源软件项目其实属于技术类项目。而大部分的商业软件项目其实属于业务导向的软件项目,适合采用领域驱动设计模式。
文章评论