DDD微服务架构设计第一课 DDD落地与实现演练

开篇词 让我们把 DDD 的思想真正落地

你好,我是范钢,曾任航天信息首席架构师,《大话重构》一书的作者。

作为互联网及大数据转型的实践者和倡导者,我先后参与过数十个国内大型软件项目,涉及国家财政、军工、税务、医疗等领域,多次参与大型遗留系统改造、系统重构等重大项目,对系统优化与改造方面有丰富的实践经验。同时,在提供架构设计、软件重构、微服务、大数据方面的培训与企业咨询的这十余年,我服务过的企业包括中国银行、中国人民银行、华为、中兴、贝尔/诺基亚、西门子、富士康等众多知名企业。

不管是做研发,还是做培训与咨询的过程中,我发现大部分公司都面临着一系列软件研发、架构转型的难题,而很大一部分难题,我认为是可能通过 DDD 来解决的。但是在实际工作中,我听到关于 DDD 的说法往往是:DDD 是很抽象,难以学习,无从下手;DDD 听着很厉害,但不能落地;DDD 真的适用微服务吗/strong> 不妨现在就请暂时放下成见和疑问,先听听我这些年关于 DDD 的故事。

我和我的 DDD

第 1 幕:我与 DDD 的美好邂逅

记得 2006 年,我怀着无比激动的心情开始研读 Eric Evans 写的《领域驱动设计》一书。这的确是本重量级的巨著,我从中学到了太多软件开发的真谛,随后也开始积极地运用在实践中。

但是,多年以后,当经历了无数软件项目的磨炼以后,我扪心自问 DDD 真正用起来了吗有,只学到了它的思想,但却没有按照它的方法去实践,这是为什么呢/strong>

DDD 是软件核心复杂性的应对之道,但当时都在忙着开发新项目,如何快速编码开发系统、快速上线才是王道,领域驱动对于客户来说太慢了。并且那个时代,业务也并没有那么复杂,DDD 远远发挥不出应有的优势。但是,最近几年,事情却慢慢发生了变化。

第 2 幕:令人心塞的遗留系统

2012 年,我接手了一个遗留系统改造的任务,该系统是 2002 年开发的,其间经历了大大小小数十次变更,程序已经凌乱不堪了,维护的成本也越来越高。此时,需要通过重构好好优化改造一下,但我发现有许多动辄数千行的大函数与大对象,是软件退化的重灾区,为什么会这样/strong>

深刻思考后,很快意识到问题的根源:这是软件的业务由简单向复杂转变的必然结果。软件会随着变更而越来越复杂、代码也越来越多,这样就不能在原有的简单程序结构里塞代码了,而是要调整程序结构,该解耦的解耦,该拆分的拆分,再实现新的功能,才能保持设计质量。

但是,怎样调整呢许第 1 次、第 2 次、第 3 次变更,我们能想得清楚,但第 10 次、第 20 次、第 30 次变更时,我们就想不清楚了,设计开始迷失方向。怎么办/strong> 我再次陷入了沉思……

经过几番苦苦的思索与探寻,我终于找到了阻止软件退化的钥匙,那就是 DDD。当系统经过数十次变更,设计迷失方向的根源还是复杂性,即业务逻辑与代码变得越来越复杂而难于理解,这不是个例,而是当今所有软件都必须得面对的难题。

运用 DDD,当系统业务变得越来越复杂时,将我们对业务的理解绘制成领域模型,就可以正确地指导软件开发。当系统变更时,将变更业务透过领域模型,还原到真实世界,再根据真实世界去变更领域模型,根据领域模型的变更指导程序变更,就能做出正确的设计,从而低成本地持续维护一个系统。这对于如今生命周期越来越长的软件系统来说,显得尤为重要。

第 3 幕:谁来拯救微服务

2015 年,互联网技术的飞速发展带给了我们无限发展的空间。越来越多的行业在思考:如何转型互联网何开展互联网业务时,一个互联网转型的利器——微服务,它恰恰能够帮助很多行业很好地应对互联网业务。于是乎,我们加入了微服务转型的滚滚洪流之中。

但是,微服务也不是银弹,它也有很多的“坑”

1.png

通过这样的一番解析,才发现微服务的设计真的不仅仅是一个技术架构更迭的事情,而是对原有的设计提出了更高的要求,即“微服务内高内聚,微服务间低耦合”。如何才能更好地做到这一点呢案还是 DDD。

我们转型微服务的重要根源之一就是系统的复杂性,即系统规模越来越大,维护越来越困难,才需要拆分微服务。然而,拆分成微服务以后,并不意味着每个微服务都是各自独立地运行,而是彼此协作地组织在一起。这就好像一个团队,规模越大越需要一些方法来组织,而 DDD 恰恰就是那个帮助我们组织微服务的实践方法。

第 4 幕:DDD,想说爱你不容易

2018 年,经过一番挣扎,我终于说服了开发团队开始使用 DDD,在这个过程中发现,要让 DDD 在团队中用得好,还需要一个支持 DDD 与微服务的技术中台

有了这个技术中台的支持,开发团队就可以把更多的精力放到对用户业务的理解,对业务痛点的理解,快速开发用户满意的功能并快速交付上。这样,不仅编写代码减少了,技术门槛降低了,还使得日后的变更更加容易,技术更迭也更加方便。因此,我又开始苦苦求索。

很快,Bob 大叔的整洁架构(Clean Architecture)给了我全新的思路。整洁架构最核心的是业务(图中的黄色与红色部分),即我们通过领域模型分析,最后形成的那些 Service、Entity 与 Value Object。

然而,整洁架构最关键的设计思想是通过一系列的适配器(图中的绿色部分),将业务代码与技术框架解耦。通过这样的解耦,上层业务开发人员更专注地去开发他们的业务代码,技术门槛得到降低;底层平台架构师则更低成本地进行架构演化,不断地跟上市场与技术的更迭。唯有这样,才能跟上日益激烈的市场竞争。

Drawing 0.png

当第一个版本上线以后,很快就迎来了第一次变更,变更的需求是增加商品折扣功能,并且这个折扣功能还要分为限时折扣、限量折扣、某类商品的折扣、某个商品的折扣。当我们拿到这个需求时怎么做呢简单,增加一个 if 语句,if 限时折扣就怎么怎么样,if 限量折扣就怎么怎么样……代码开始膨胀了。

接着,第二次变更需要增加 VIP 会员,除了增加各种金卡、银卡的折扣,还要为会员发放各种福利,让会员享受各种特权。为了实现这些需求,我们又要在 payoff() 方法中加入更多的代码。

第三次变更增加的是支付方式,除了支付宝支付,还要增加微信支付、各种银行卡支付、各种支付平台支付,此时又要塞入一大堆代码。经过这三次变更,你可以想象现在的 payoff() 方法是什么样子了吧,变更是不是就可以结束了呢实不能,接着还要增加更多的秒杀、预订、闪购、众筹,以及各种返券。程序变得越来越乱而难以阅读,每次变更也变得越来越困难。

Lark20201116-102936.png

但是,真实情况却不是这样的。真实情况是,起初我们拿到的需求是那个简单需求,然后在简单需求的基础上进行了设计开发。但随着软件的不断变更,软件业务逻辑变得越来越复杂,软件规模不断扩大,逐渐由一个简单软件转变成一个复杂软件。

这时,如果要保持软件设计质量不退化,就应当逐步调整软件的程序结构,逐渐由简单的程序结构转变为复杂的程序结构。如果我们总是这样做,就能始终保持软件的设计质量,不过非常遗憾的是,我们以往在维护软件的过程中却不是这样做的,而是不断地在原有简单软件的程序结构下,往 payoff() 方法中塞代码,这样做必然会造成软件的退化。

也就是说,软件退化的根源不是软件变更,软件变更只是一个诱因。如果每次软件变更时,适时地进行解耦,进行功能扩展,再实现新的功能,就能保持高质量的软件设计。但如果在每次软件变更时没有调整程序结构,而是在原有的程序结构上不断地塞代码,软件就会退化。这就是软件发展的规律,软件退化的根源。

杜绝软件退化:两顶帽子

前面谈到,要保持软件设计质量不退化,必须在每次需求变更的时候,对原有的程序结构适当地进行调整。那么应当怎样进行调整呢是回到前面电商网站付款功能的那个案例,看看每次需求变更应当怎样设计。

在交付第一个版本的基础上,很快第一次需求变更就到来了。第一次需求变更的内容如下。

增加商品折扣功能,该功能分为以下几种类型:

  • 限时折扣

  • 限量折扣

  • 对某类商品进行折扣

  • 对某个商品进行折扣

  • 不折扣

以往我们拿到这个需求,就很不冷静地开始改代码,修改成了如下一段代码:

Drawing 4.png

“两顶帽子”的设计方式意义重大。过去,我们每次在设计软件时总是担心日后的变更,就很不冷静地设计了很多所谓的“灵活设计”。然而,每一种“灵活设计”只能应对一种需求变更,而我们又不是先知,不知道日后会发生什么样的变更。最后的结果就是,我们期望的变更并没有发生,所做的设计都变成了摆设,它既不起什么作用,还增加了程序复杂度;我们没有期望的变更发生了,原有的程序依然不能解决新的需求,程序又被打回了原形。因此,这样的设计不能真正解决未来变更的问题,被称为“过度设计”。

有了“两顶帽子”,我们不再需要焦虑,不再需要过度设计,正确的思路应当是“活在今天的格子里做今天的事儿”,也就是为当前的需求进行设计,使其刚刚满足当前的需求。所谓的“高质量的软件设计”就是要掌握一个平衡,一方面要满足当前的需求,另一方面要让设计刚刚满足需求,从而使设计最简化、代码最少。这样做,不仅软件设计质量提高了,设计难点也得到了大幅度降低。

简而言之,保持软件设计不退化的关键在于每次需求变更的设计,只有保证每次需求变更时做出正确的设计,才能保证软件以一种良性循环的方式不断维护下去。这种正确的设计方式就是“两顶帽子”。

但是,在实践“两顶帽子”的过程中,比较困难的是第一步。在不添加新功能的前提下,如何重构代码,如何调整原有程序结构,以适应新功能,这是有难度的。很多时候,第一次变更、第二次变更、第三次变更,这些事情还能想清楚;但经历了第十次变更、第二十次变更、第三十次变更,这些事情就想不清楚了,设计开始迷失方向。

那么,有没有一种方法,让我们在第十次变更、第二十次变更、第三十次变更时,依然能够找到正确的设计呢,那就是“领域驱动设计”

保持软件质量:领域驱动

前面谈到,软件的本质就是对真实世界的模拟。因此,我们会有一种想法,能不能将软件设计与真实世界对应起来,真实世界是什么样子,那么软件世界就怎么设计。如果是这样的话,那么在每次需求变更时,将变更还原到真实世界中,看看真实世界是什么样子的,根据真实世界进行变更。这样,日后不论怎么变更,经过多少轮变更,都按照这样的方法进行设计,就不会迷失方向,设计质量就可以得到保证,这就是“领域驱动设计”的思想。

那么,如何将真实世界与软件世界对应起来呢样的对应就包括以下三个方面的内容:

  • 真实世界有什么事物,软件世界就有什么对象;

  • 真实世界中这些事物都有哪些行为,软件世界中这些对象就有哪些方法;

  • 真实世界中这些事物间都有哪些关系,软件世界中这些对象间就有什么关联。

Drawing 0.png

最后,我们对订单可以进行“下单”“付款”“查看订单状态”等操作。因此形成了以下领域模型图:

Drawing 4.png

通过领域模型的指导,将“订单”分为订单 Service 与值对象,将“用户”分为用户 Service 与值对象,将“商品”分为商品 Service 与值对象……然后,在此基础上实现各自的方法。

商品折扣的需求变更

当电商网站的付款功能按照领域模型完成了第一个版本的设计后,很快就迎来了第一次需求变更,即增加折扣功能,并且该折扣功能分为限时折扣、限量折扣、某类商品的折扣、某个商品的折扣与不折扣。当我们拿到这个需求时应当怎样设计呢显然,在 payoff() 方法中去插入 if 语句是不 OK 的。这时,按照领域驱动设计的思想,应当将需求变更还原到领域模型中进行分析,进而根据领域模型背后的真实世界进行变更。

Drawing 8.png

在该设计中,将折扣功能从付款功能中独立出去,做出了一个接口,然后以此为基础设计了各种类型的折扣实现类。这样的设计,当付款功能发生变更时不会影响折扣,而折扣发生变更的时候不会影响付款。同样,当“限时折扣”发生变更时只与“限时折扣”有关,“限量折扣”发生变更时也只与“限量折扣”有关,与其他折扣类型无关。变更的范围缩小了,维护成本就降低了,设计质量提高了。这样的设计就是“单一职责原则”的真谛。

接着,在这个版本的领域模型的基础上进行程序设计,在设计时还可以加入一些设计模式的内容,因此我们进行了如下的设计:

Drawing 12.png

有了这些领域模型的变更,然后就可以以此作为基础,指导后面程序代码的变更了。

支付方式的需求变更

同样,第三次变更是增加更多的支付方式,我们在领域模型中分析“付款”与“支付方式”之间的关系,发现它们也是软件变化不同的原因。因此,我们果断做出了这样的设计:

Drawing 16.png

通过加入适配器模式,订单 Service 在进行支付时调用的不再是外部的支付接口,而是“支付方式”接口,与外部系统解耦。只要保证“支付方式”接口是稳定的,那么订单 Service 就是稳定的。比如:

  • 当支付宝支付接口发生变更时,影响的只限于支付宝 Adapter;

  • 当微信支付接口发生变更时,影响的只限于微信支付 Adapter;

  • 当要增加一个新的支付方式时,只需要再写一个新的 Adapter。

日后不论哪种变更,要修改的代码范围缩小了,维护成本自然降低了,代码质量就提高了。

总结

这一讲通过以上的过程,我们演练了如何运用 DDD 进行软件的设计与变更,以及在设计与变更的过程中如何分析思考、如何评估代码、如何实现高质量。后面,我们将演练如何将领域模型的设计进一步落实到软件系统的微服务设计与数据库设计。


文章知识点与官方知识档案匹配,可进一步学习相关知识云原生入门技能树首页概览8582 人正在系统学习中

来源:办公模板库 素材蛙

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年4月14日
下一篇 2022年4月14日

相关推荐