70z

《 复杂软件设计之道 》Epub-Pdf-Mobi-Txt-Azw3 下载在线阅读

 《 复杂软件设计之道 》Epub-Pdf-Mobi-Txt-Azw3 下载在线阅读




领域驱动设计全面解析与实战


内容简介:

同时引入了DDD的*新发展成果,如事件风暴建模,并以此建模方式替代传统的DDD建模方式讲解了多个案例。还涉及大量软件系统实现相关的技术和架构,读者在学习DDD的同时,也可以掌握这些技术、架构在DDD实现中的灵活应用。
另外,每个概念或方法的讲解过程都穿插了具体实例,以方便读者结合实例进行学习;第2~7章每章*后都有总结与拓展,将本章涉及的案例和知识进行总结,并引入国际DDD专家的心得经验,试图告诉读者一条DDD实战中行之有效的途径。
《复杂软件设计之道:领域驱动设计全面解析与实战》主要面向拥有一定实践经验的软件产品经理、领域专家、软件设计开发相关从业人员,相关初级从业者也可阅读本书。

epubooks.top站是一个下载优质电子书的网站,书籍种类非常多,每个类目下的书籍资源都非常丰富,支持kindle、epub、mobi、azw3、pdf格式下载。以及在线阅读-epub,kindle,mobi,azw3,pdf格式!


一個下載優質電子書的網站,書籍種類非常多,每個類目下的書籍資源都非常豐富,支持kindle、epub、mobi、azw3、pdf格式下載。以及在線閱讀-epub,kindle,mobi,azw3,pdf格式!


=======================================================



记得收藏本站哟!每天都会更新

资源收集不易,还请帮忙点一点,是我的动力谢谢!!!!!!!!!!

如果有什么书本站没有,你也可以在评论处留言。我会第一时间去的!

收藏本站每日更新更多书籍!

资源地址: Epub版-----网盘密码1122

  MOBI版-----网盘密码1122

            PDF版------网盘密码1122

                  TxT版------网盘密码1122

    azw3版------网盘密码1122

=======================================================

部分简介:

领域驱动设计的特点

DDD的特点主要是定位于解决复杂性,解决复杂性的方法本身可能让初学者感觉复杂,正如牛顿力学让当时的人们难以理解一样,而现在人们在初中阶段都会学习牛顿力学,而且感觉并不是很复杂。因此,复杂性是相对而言的,本书的目的正是起到普及DDD的作用。

DDD解决复杂性的方法是积极面对,尽早发现复杂核心,划分边界,分而治之。虽然这种方法简单直接,但是由于软件本身发展的复杂性,人们的视线常常被误导,人们将解决复杂性、降低软件债务、提高软件质量的视线放在了技术手段上,而忽视了业务领域本身以及人自身的相互沟通和思维方法等因素。

DDD让人们将软件开发的重点从技术本身转移到需要解决的问题上,发现问题、理解问题才能解决问题。问题在哪里?关键点在哪里?复杂性在哪里?这些都需要辨识。问题的理解则更难,盲人摸象各执一词,如何达成共识成为关键。

发现和理解问题以后,如何解决问题就是关键,划分领域边界、有的放矢、纲举目张是其重要方法。

复杂性无处不在,有些存在于问题之中,有些存在于问题的理解之中,有些是解决问题时带来的,针对这些问题,DDD都有独特的应对之道。

1.2.1 发现和理解问题

DDD的革命性在于,它提出了面向业务领域的软件设计,也就是以业务为驱动的设计。可能有人产生疑问,还有不是面向业务领域的设计吗?其实,在软件设计中,很多人不知不觉采用了以技术为驱动,或者以数据为驱动的方式。这些都是软件开发过程中必不可少的环节,但是它们还是为了实现业务需求。软件设计中往往可能陷入对技术细节,或者陷入某些流行技术的狂热,手里有了一把锤子,看到什么都是钉子,为技术而技术,让最聪明的人员去攻克技术难题,而不是业务建模难题;同时,技术人员为了让自己的简历更充实,也乐意尝试一个个新技术,增加自己的技术使用经验,这样就很少有精力放在对业务需求的分析上了。

另外,技术团队在交流沟通中更习惯于使用技术术语(如字符串、整数、Map、List、循环等),而不是使用业务术语。例如,一个程序员通常会对另外一个程序员说:“使用一个Map就可以了,使用一个表来存储它就可以了。”这些都是技术术语,用技术抽象来替代业务抽象,使得组织内行话变成“技术黑话”,即失去了一些业务含意。

DDD认为,技术架构的选择需要服从具体业务特点,如果抛开业务特点,那么通用意义上的“银弹”几乎是不存在的。没有一件衣服适合所有人穿,这也是解决方案的特点。

只有面向业务需求,才能使用面向对象的分析设计方法提炼出业务对象模型。当然,具体实现时不一定用面向对象编程(OOP)语言,也可以用函数式语言。所以,首先要瞄准业务需求中的问题。

问题空间是解决问题的目标所在,有了问题空间,才能提出解决方案,而解决方案的提出有赖于人们对问题知识的理解。这种理解是非常主观的,甚至可能是片面的,而且它会随着时间或新的见解发生变化。这些见解可以来自业务方面或软件工程师。这是一项集体努力,这一事实构成了DDD中最大的必须面对的问题,正如Alberto Brandolini所说:“不是领域专家的知识进入了生产中的软件,而是开发人员自己的见解悄然渗透到了软件中。”

对问题空间中业务知识的理解不足与没有达成共识成为软件开发中的最大障碍,那么如何解决呢?

首先需要分析一下人们是如何描述问题空间的。对问题空间的描述有多种形式,如文字文档、用例场景图或会议形式。

无论文字文档还是用例场景图,这些文档的最大问题是缺乏及时性,常常会过时或与公司的集体看法不一致,也带有编写者自身的偏见;另外,文档确实在知识获取中至关重要,但是如果没有记录为什么做出其中决策的历史知识,很难创建当前架构的文档历史轨迹并使其保持最新。用例场景图等可视化形式有时并不一目了然,还是需要绘图者自己解释。他省略了哪些细节?为什么省略?他所取的抽象角度是否遗漏了重要信息?打个比方,这位绘图者也许只画一只大耳朵表示是大象,让人从大耳朵联想到大象——什么动物的耳朵这么大?八成是大象。这不是表达问题空间的精确方式,联想思维与精确分析思维是对立的,比喻、形象化这些几乎是联想思维的代名词,这实际上是一种模糊上下文边界的跳跃式思维,虽然对于学习知识有入门定位的帮助,但是深入理解知识还是必须深入事物内部和结合事物所处的上下文环境。

当然,有些企业可能都没有这些业务文档,实际上可能就没有产品经理,没有业务架构,那么只能从头开始收集公司内部的集体知识。这种收集方式有很多,如面对面访谈、成立专门领导小组、召开需求研讨会、头脑风暴与思维还原、角色扮演等。具体收集渠道还有观察与工作见习、原型演练、问卷调查、电子访谈、遗留代码分析(逆向工程)、阅读代码或其文档等。

这里介绍两种发现问题并能就理解问题达成共识的方式:面对面协作建模和阅读代码逆向工程建模。

(1)面对面协作建模

面对面协作建模的要点是:需要邀请掌握整个业务线的合适人员,并参加4~6小时的头脑风暴会议。这个会议的召集是存在难度的,参会人员同时有空的概率很小,需要平衡参加会议的合适人选。最忙的人可以不参加会议,可依靠甲方的IT负责人来筛选合适的人选,并找到一个“最佳点”——只要从他们那里可以获得最多领域知识即可。

如何让受邀者积极参与这次会议?如果能弄清楚他们的痛点并将其与参加本次研讨会的好处联系起来,他们就会更愿意参加,并可采取主动上门访谈的方式,这样就能让他们明白研讨会能够解决他们的痛点。

同时,需要让会议对所有相关人员保持开放,欢迎他们自己主动加入,包括性格保守的开发人员或“自卑”的测试人员。

参加会议的人数保持在30人左右,在这个会议里,每个人将领域中发生的事件活动写在墙上,并应按照这些动作事件发生的先后顺序排列。当人们写出发生的事件时,慢慢会谈到其背后存在的流程,而对流程讨论的深入意味着对企业业务规则的认识也在加深,例如在电商系统中,人们发现其中有以下领域事件。

1)用户将商品放入购物车的事件。

2)订单确认生成的事件。

3)支付完成的事件。

4)发货完成的事件。

在时间顺序中,挑选商品放入购物车的事件应该是在订单生成之前发生的,而订单生成以后才能进行支付。问题来了,订单生成以后必须支付吗?可以先发货后支付吗?不同的企业也许不同,这是不是每个企业自身的业务规则呢?如果企业的业务规则是必须先支付,那么规则决定了流程,而流程决定了事件的发生先后。

召开几十人的大型头脑风暴会议只能对问题空间中业务规则的理解达成一致,对基本业务名词术语形成普及或达成共识,但是不能替代个人学习。对问题空间的深入认识很难通过大会形式解决,需要每个人自行学习。

(2)阅读代码逆向工程建模

在这里,阅读代码逆向工程建模方法可能就对个人“修行”有独特的帮助。DDD通常是在软件系统的第2版引入,因为这时大家已经通过1.0版本基本摸清了问题空间所在,在这种情况下,可以通过阅读1.0版本代码,使用UML建模工具进行逆向工程建模,将琐碎的代码或详细设计文档使用简要的UML图表达。这其实是一种浓缩业务领域重点的过程,在这个逆向过程中,会促使参与者不断重新思考问题空间,如这个代码模型真的能代表问题空间中的知识模型吗?它省略了什么地方?忽视了什么?是不是这种忽视导致代码模型偏离了知识模型?

例如,下面是Product类的Java代码:




通过逆向工程转换为UML类图,如图1-1所示。

当然,对于一个复杂大型项目,一个类一个类地转换还是相当琐碎的,需要根据系统模块进行划分,从每个模块中找到那些实体代码,找出关键实体来代表这个模块。当然,可能原来的系统就是一个“大泥球”,那么这时还需要根据服务代码功能还原用例场景图。例如一个订单系统有商品服务和订单服务,这两个代码中揉入了具体代码框架等技术细节,那么就根据服务的接口进行逆向工程迭代,使用UML实现图1-2所示的顺序图。

在这个逻辑顺序图中,将功能的实现前后步骤抽象了出来,比如商品服务(Product Service)主要完成商品的新增、修改或查询功能,然后是用户进行下单、支付,商家进行发货,这样动作发生的时序就能一目了然,有助于分析这些动作之间是不是有必然的前后因果,如只有商品发布以后才能下单,这两者有强烈的因果关系,前后顺序不能颠倒,而下单、支付和发货之间则没有这种因果制约,有可能是货到付款,也有可能收款再发货。到底是先付款再发货,还是可以发货后再付款,这些取决于企业本身的业务规则。有的电商企业对回款要求比较高,不能承受欠款的资金压力,就只能选择先付款后发货,但是考虑到付款和发货之间不存在天然的因果关系,设计的模型必须支持可拓展到发货后再付款这个模式。详细设计后面章节会涉及。



这里通过逆向工程发现了企业的业务规则,通常这隐藏在问题空间中,需要深入挖掘。没有业务规则的软件系统就只是一个CRUD系统而已,正是由很多业务约束和规则才使得简单的CRUD系统变得复杂,这是必须在问题空间中首先感知的要素。业务规则也是通过头脑风暴会议碰撞以后才能发现,因为表面上大家在会议中讨论的是领域中发生的事件,实际上是讨论了流程的合理性,而判断合理性的标准是企业制定的业务规则。

发现问题空间的业务策略和业务规则是DDD瞄准的目标(业务策略与规则见后面章节),找到目标以后,就要调整准星,以便精确瞄准问题空间中的复杂性问题。

1.2.2 领域即边界

既然DDD认为技术必须服从业务,那么业务本身的特点是什么?有没有一种通用业务设计适合所有行业?能否通过不同配置就能适合特定的业务场景呢?其实业界也在不断探索和尝试这条路,甚至通过画流程图就能自动生成特定的业务流程系统,其实这些方法论或系统在初期业务简单的情况下还是行得通的,但是随着时间推移和系统复杂性的增加,此路就行不通了。

例如,ERP最初是为制造业创建的资源管理系统,以实现机器和原材料等资源的规划和调拨。由于工厂不同,ERP系统可以高度配置,以满足不同工厂的需求。现在,这一点被夸大为针对任何业务领域只要一次性配置就能运行,实际上背后仍然是相同的资源流系统。但是当将一种业务领域变成另一种业务领域时,会忽视每种业务领域的具体特点,这样系统就变得越来越不精确了。因此,边界概念很重要,领域这个词语本身就带有“边界”的概念。

名称是认识万物的第一步,从“无名”跨越到“有名”,其中的重要一步就是划分边界。因此,“名称”和“边界”两者是互为联系的,当确定事物的边界后才能给它命名,也可从名称本身大概判断其边界或作用范围。

编程语言中有一个术语称为“作用域”,英文为scope(其实也是边界的意思),它是指变量的作用边界是多大,从哪里开始、从哪里结束,如果作用域是函数方法内,那么就是从函数方法开始执行到其结束这段范围内。

命名和划分作用域很重要,同时也很难,所以有人说“命名和缓存失效是计算机中最难的两件事情”(缓存失效有关数据一致性问题,本书后面的技术架构中也会详细涉及)。

为什么命名很难呢?因为划分边界很难;为什么划分边界很难?因为边界这个圈是主观与客观的结合。边界是人们认识客观世界的一个媒介,带有主观倾向,同时又具有客观自然性,这就很难把握了。

通常根据分类法进行边界划分,将事物分门别类是划分边界的主要方式。但是这种分类标准受各种因素影响,包括人类自己的法规,例如前面谈到的垃圾分类,干垃圾和湿垃圾的是依据政府发布的管理条例进行定义的。再举一例,番茄属于蔬菜还是水果呢?从植物科学角度看,番茄是一种水果,它完全符合植物学定义的水果含义。但是,美国最高法院在1893年的尼克斯起诉海登一案中裁定:番茄属于蔬菜,因为需要对进口蔬菜种植者征税,番茄就被法院裁定为属于蔬菜了。

所以,领域的边界有时是模糊的,它是与该事物所处的上下文相关的:在科学这个上下文中,番茄属于水果;在烹饪这个上下文中,番茄属于蔬菜。

现在已经从领域讨论到边界,再次深入到“上下文”这个概念。值得注意的是,这里谈到了两种发现边界的方式:一种是通过客观事物的自然边界来发现,也就是事物自身(内部)的强烈结构特征所显示的边界;还有一种是通过客观事物所处的(外部的)上下文环境发现其边界。

例如,上市公司的股票价格到底代表什么?代表这家公司的价值,还是取决于股票市场表现出的价格?应该说两者都有,价值是由这家公司的内在质量决定的,而价格则是由其所处的股票市场决定的。人们常说价格围绕价值波动,其意思是:外在上下文的定价围绕着其内在价值波动。这在一定程度上是有道理的,但是也不一定,例如房价主要取决于地段,这个价格几乎不会围绕房子的内在质量波动,就是到了经济大萧条时期,它们的价格差比例也不会改变。从这些经济现象中可以看出,事物内在结构和事物所处外部上下文是决定事物边界的两个基础因素,并没有哪个更重要的区分。也正是基于这两种认识途径,才有了“价值”和“价格”两种不同的命名。

至此总结出一个核心概念:领域即边界,边界靠分类,分类需要从内外部入手。

DDD就是一种不断追寻业务边界的设计思想、方法和活动,这种设计活动是一种主观与客观不断迭代的认识演变过程。

图1-3所示为通过机器学习识别公路照片的边界和名称。



1.2.3 解决复杂性

前面介绍的两点合起来实际上是为了解决业务领域中的复杂性问题,当业务不是很复杂时,可把精力用于钻研各种流行技术,交流沟通中可用各种技术名词替代业务术语的表达,也可以使用面向数据的分析方法将更多精力投入大数据的分析之中。

如果不能掌握各种技术方面的复杂性,那么就可能得到一个不太有用的系统。但是,如果无法掌握领域的复杂性,就会得到一个接近于零价值的系统。

例如,开发一套货物托运系统,领域的复杂性可能是这个系统的关键(可能技术架构也很复杂)。如果无法正确表达货物从物流公司发送到车队以及装载工具在哪些地点装卸的细节,那么货物就可能无法及时进行正确的托运,或最终导致错误托运。

在这种情况下,理解和建模货物处理领域应该是建模工作的重点,但是如果这时花时间去优化数据库连接池肯定是很糟糕的选择。因为,关键的复杂性是领域,如果不能解决关键的复杂性就会使得任何技术高超的解决方案都变得毫无意义。

那么什么是复杂性?

中文没有对复杂这个词语再进行详细分类,英文中复杂对应两个单词:Complex和Complicated,这两个词语都有复杂的意思,Complex是Simple的反义词,而Complicated是Easy的反义词。如果某事物复杂,意味着其内部结构不简单(Simple),可能由很多组件或部件组成,Complex代表事物内部(本身)具备天然的复杂性;而某个事物Complicated代表其不容易被使用,这已经和人的主观有关,这种复杂可能因人而异。软件工程中消除的是Complicated,包括解决方案和代码要编写得让别人更容易读懂,当然这涉及很多针对复杂性的模式化处理,而模式是大家都明白的套路,如同武术中的基本套路一样,大家交流起来就容易得多。

对于业务领域中客观存在的复杂性,只要以一定的层次结构分解成复杂(Complex)的组件就能解决。一辆汽车的配件可能成千上万,那就分而治之,每个人完成几个部件的组装,且不相互影响。

所以,解决复杂性的两种方法是:拆解成松耦合的组件+使用容易让人明白的套路表达出来。

DDD是怎么实现这两种方法的呢?

首先,DDD通过引入“领域或子域”以及“有界上下文”来划分边界,边界一旦划分好,拆解的第一步就能完成;其次,DDD引入各种模式名词,比如聚合、实体、值对象、工厂、仓储、领域事件,让知晓这些模式的人能够一下子定位到功能对应实现的组件。随着DDD的普及,这些名词已经逐步为大家熟知,对于理解一个DDD软件系统来说,熟知这些模式的人就不会感到其复杂(Complicated)了。

当然,对于初学者,理解DDD这些模式名词可能觉得比较难或复杂,DDD相比数据库表设计或事务过程化脚本设计等传统设计方法,学习门槛确实比较高,但是随着业务领域复杂性的提高,使用DDD设计的软件系统应对需求变化和复杂性增加的能力也会提高,即软件交付效率高,反馈周期短;而使用传统设计方法虽然入手很快,用增删查改就可以马上搞定一个简单系统,但是随着业务复杂性的提高,代码的复杂度越来越高,可扩展性、可维护性也会越来越差。例如,新增记录时需要很多业务规则检查,修改也有权限要求,需要有各种数据的一致性、完整性检查,在使用数据库表设计时,会将这些需要数据检查的表放在一个库中,但是当表的数量很多时,不但会出现事务锁导致的性能问题,而且会产生单点风险,某个表的操作有一点问题就会使其他表不能被操作,同时带来代码的复杂性(Complicated),这么多表的操作语句如同大泥球一样混在一起,代码发生紧耦合,让人理解起来不方便,新手要了解一个功能时,需要把其他相关功能都看一遍,因为这些功能代码都放在几个事务脚本中(如服务的函数方法中),修改起来牵一动百,由此带来的工作量是多么巨大可以想象到。这种耦合在一起的大泥球也称为单体架构,代码库是统一的一个,数据库是统一的一个,服务是一个大服务,几十人围绕这样一个单体系统进行迭代开发,争夺同一处资源,其效率和生产力可想而知。

围绕一个Git库进行编程时,大家最讨厌的是别人使用force强行Push,这样自己的代码可能被覆盖,那么为什么不把代码库分开呢?

将代码库分开需要首先将业务领域切分开,形成有层次的Complex结构,单体系统由于天然具有各种复杂性,催生了微服务架构。微服务是微小服务的意思,微小的边界是多大?多小算微小?这些都需要依赖DDD这套模式方法对业务领域进行切分。

对于业务复杂性的判断有以下依据可供参考。

1)系统是否有类似于CRUD的接口,是否由领域专家以CRUD术语描述?

如果是,则代表简单。

2)业务逻辑是否围绕输入验证?

如果业务规则只是对输入进行验证,没有自己独特的业务规则验证,则属于简单。

3)有复杂的算法和计算吗?

很显然,,如果有,就属于复杂了。

4)是否有应该执行的业务规则和不变量?

拥有系统自己的业务规则,这种业务规则是为了实现业务战略的,并且通过复杂的流程来保证,很显然比较复杂。

5)是否有复杂的If…else判断?结果代码的条件复杂度是什么?它有许多不同的执行方案吗?

如果是,则属于复杂;如果这种判断影响全局,那就属于更复杂了。

1.2.4 新的数据结构设计方式

业界对数据结构和算法哪个更重要争论已久,埃里克雷蒙德2003年在其UNIX哲学中提到,数据占主导地位。如果选择了正确的数据结构并组织好了,那么算法几乎总是不言自明的。笔者认为,数据结构是编程的核心,而非算法。

因此,需要拥有一套设计数据结构/数据表的方式,而DDD正是其中之一。

当然,ER模型的数据分析方法也是直接设计出数据表结构的方法,但是由于其和具体关系数据库相关,在如今NoSQL数据库流行的时代,ER模型分析方法无疑需要发展。其实在ER模型中有一种星形模型,非常类似DDD的聚合模型,如图1-4所示。


图1-4 DDD与ER两个方式的对比

DDD中的领域事件集合等同于ER中的明细表,明细表(事件)是造成主表(聚合根)变动的原因,假如主表是个人账户,而明细表代表进出明细,那么每发生一笔进出,个人账户的余额状态就会发生变动。

例如,如果个人账户余额初始是100元,今天进账30元,出账消费20元,那么个人账户的余额就是100+30-20=110元,状态值从100元变成了110元。

从ER数据库角度看:进账30元和出账20元属于进出明细,从DDD领域事件角度看,它们属于发生的两次事件,发生了进账30元事件和出账20元事件。

因此,数据库设计师或DBA等将知识结构发展到DDD是一种非常自然的方式。

当然,DDD不只是新的数据结构设计方式,还能将数据结构的算法和操作方式加入其中。DDD可以说是比ER模型设计更广泛的一种设计方法。

DDD设计结果主要是通过类(class)来表达其模型,类不仅仅是一种数据结构,而且带有主动操作数据结构的行为,类=数据结构+行为。如图1-5所示即为一个订单聚合案例图。

在订单这个聚合中,有订单条目、地址等业务对象,类图表达了订单和订单条目、地址之间的结构关系,虽然它是一张平面图,但是有主从关系,订单是父节点,其他都是子节点。这张图非常类似于数据表结构中的星形模型图,但是又包含比静态数据结构更多的信息,例如在订单中可能会有一个总价计算的函数,用来计算整个订单条目累计的总价格,这称为维持订单规则的不变性。不变性的规则指订单条目中各个商品价格的累计之和应该等于这个订单的总价格。


发表评论

0 评论