DDD领域驱动篇——第一章(一文带你领略DDD、微服务和中台设计)

        在讲DDD之前,我对领域驱动曾经有过一段时间的了解,其实这个概念当我第一次听的时候发现很泛化,而且很抽象甚至难以理解,后来我发现这个玩意得需要很多时间、很多框架、技术的演进、软件迭代到了一定的瓶颈,业务愈发复杂而带来一系列架构转变和业务重构的捶打,才可以更好的体会和理解这些新词的意思,本章我就讲下我对DDD的理解和认识,也需要读者能够熟悉一些常用的设计模式,希望你不是个小白而是一个在业务或者技术有一定的认识,这样可能看起来不会费劲,也为后面更好的应用于大型互联网实战中更上一层

1、前言

        说起DDD的实践,那就不得不提微服务了。2016年,我刚开始接触微服务,那时候但是那个时候对技术的热衷,和身边的人聊微服务的设计理念,接受度并不高,自己对微服务的理解也没有现在熟悉,毕竟大家普遍采用的还是集中式架构。

        早在今年中旬,为了应对市场需求,我很幸运的参加了公司大型互联网的架构到当下主流微服务架构转变的过程,业务的日渐复杂也是可以预见的,微服务的价值确确实实存在。而这个过程中,我和我的技术团队踩过不少坑,最尖锐的一个问题是:“微服务到底怎么拆分 和设计才算合理,拆多小才叫微服务?”而微服务的边界历来也是最容易产生争议的地方。

        随着阿里巴巴成功完成了中台战略转型。于是,很多大型公司也开启了中台数字化战略转型,中型公司也根据自身需求跃跃欲试。但也有很多公司由于历史原因,存在着大量系统重复建设的问题。

        作为中台,需要将通用的可复用的业务能力沉淀到中台业务模型,实现企业级能力复用。因此中台面临的首要问题就是中台领域模型的重构。而中台落地时,依然会面临微服务设计和拆分的问题。这两个问题一前一后,放在任何一家公司,我想都是一个不小的挑战,这也是我一直在探索和解决的问题。这两年,中台越来越火,微服务越来越热,参与的人越 来越多。那是否有好的方法来指导中台和微服务的设计呢?一次偶然的机会我接触到了 DDD,深入研究后,我发现,运用 DDD 设计思想实现的微服务边界确实清晰很多,业务领域划分也十分合理,而我也希望能够运用在未来我所在的任何一个领域中。古话说,山不向我走来,我便向它走去。哈哈

2、初探DDD

        早在2003年就诞生的DDD,06年Eric Evans就著出《领域驱动设计》一书,怎么会“迟到”近20年才大热的微服务设计呢?DDD全称是Domain Driven Design,叫领域,驱动,设计;那和我们常说的中台,微服务之间的关系是什么样的呢,我想很多人是不明白的甚至会混为一谈。

  • 中台的本质是业务模型
  • 微服务是业务模型的系统落地
  • DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计

        三个概念转为我们可以理解的一句话:我们要用DDD的设计理念,搭建一个一个的中台业务模型(业务中台,技术中台,数据),最终以微服务相关的技术实现系统落地!!

        它们之间就是这样的一个铁三角关系。DDD 强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计

        通过战术设计,我们会从领域模型转向微服务设计和落地。此时,边界清晰、可持续演进的微服务架构雏形就在你面前了。

        所以DDD概念老早提出,Get到他的思想,却到现在很多公司没有按照它的方法去实践,这是为什么呢?主要因为复杂度。

3、令人心塞的遗留系统

        DDD是软件核心复杂性的应对之道,而每家公司都在忙着开发新项目,快速迭代新功能快速上线才是王道!领域驱动对公司,客户群体来说,太慢了,并且那个时代,业务也没有这么复杂,DDD远远发挥不出来应有的优势。但是最近几年,事情却慢慢发生了变化,很多公司的产品迭代至今,程序以及凌乱不堪,维护成本越来越高,需要重构优化改造。

        许多动辄数千行甚至上万行的大函数和大对象,是软件退化的重灾区,为什么会这样?深刻思考后很快意识到了根源:这是软件业务由简单像复杂转变的必然结果,软件会越来越复杂,代码会越来越多,这样就不能在原有的程序结构里塞代码,而是要调整程序结构,该解耦的解耦,该拆分的拆分,再实现新功能才能保证设计质量

        那么怎样调整呢,也许第一次第二次我们想的清楚,但是第十次三十次我们就想不清楚了,设计开始迷失方向怎么办?再次陷入了沉思;这个时候,DDD就可以很好的解决这些问题。业务越来越复杂而难以理解,这不是个例而是当今所有软件都必须面对的难题,运用DDD当系统业务变得越来越复杂时,将我们对业务的理解绘制成【领域模型】可以正确的指导软件开发。

        当系统变更时,将变更业务通过领域模型,还原到真实世界,再根据真实世界去变更领域模型,根据领域模型的变更指导程序变更,从而低成本的维护一个系统达到快速迭代,快速交付的合格软件。这是DDD的设计核心,这么说是比较抽象的,我举个例子,如果我要设计一个支付交易系统,从应用代码层面,如果我要扣款的时候新增加很多功能如vip,限时折扣,某个/类商品折扣,很快就迎来了第一次的需求变更,我们最大概率会这么做,在原有的付款方法中加入if语句,然后判断逻辑;如果对于领域驱动的思想,还原成真实世界,再去修改领域模型,分析付款和折扣之间在现实世界的关系,你可能会认为折扣是在付款过程中进行的,所以你会认为折扣应该写在付款中,这样思考对吗,回顾下我们软件设计的【单一职责】原则,软件系统中每个元素只完成自己职责范围的事情,而将其他的事情交给别人去做,我只是调用,重要是我们如何理解这个【职责】,以往的理解是我要做某件事,那么与此相关的所有事情都是它的职责。因为这个错误理解带来了很多错误设计,从而将折扣写到付款功能中。

        一个职责是一个软件变化的原因。当用户提出一个需求变更时,为了实现这个变更,而修改的软件成本越低,软件设计质量就越高。如果为实现这个需求,需要修改三个模块,完后这三个模块都要集体测试,其维护成本非常高,而怎么样才能把修改降到最低呢,最现实的方法就是只修改一个模块,怎么样才能在每次变更的时候就修改一个模块实现新需求呢?这就需要平时不断的整理代码,将因同一个原因而变更的代码都放在一起,将因不同原因而变更的代码放在不同的模块、类中。

        回到刚刚的案例中,只需要回答两个问题:

  • 当付款发生变更时,折扣是不是一定要变?(否)
  • 当折扣发生变更时,付款是不是一定要变?(否)

        当这两个问题的答案都是否定的时候,那付款和折扣就是这个软件变化的不同原因,那放在同一个方法,同一个类中合适吗?很明显不合适,就应该将折扣从付款中提取出来,单独放在一个类中。所以根据折扣我们可以单独拆出来形成一个独立的真实世界,来绘制领域模型,如下:

        没有领域驱动之前添加折扣,很多人会把折扣写在付款逻辑中。

         添加折扣后我重新折扣进行了分析,对领域模型进行了修改。以折扣接口为基础,以此设计了很多不同类型的折扣方式,这样的设计当付款功能发生变更时,不会影响折扣,而折扣发生变更时也不会影响付款,而折扣之间也不会存在相互影响,其实这就是策略模式的体现!!

         紧接着会在这个版本的基础上进行程序设计,设计的时候加入设计模式的内容,将折扣功能做成了折扣策略接口,与各种折扣策略的实现类,当哪个折扣类型发生变更时,就修改哪个折扣策略的实现类,当要增加新的类型就再写一个新的策略实现类,从而设计质量得到提高。

         于是第二个业务场景过来了,我要增加vip会员功能,对不同类型的vip会员进行不同折扣,付款的时候为vip发福利,享受特权等。

         拿到这个需求我们又应该如何设计呢?同样先回到领域模型,分析用户和vip会员的关系,付款与vip会员的关系。回答两个问题:

  • 用户发生变更时,vip会员是否一定要变(否)
  • vip会员发生变更时,用户是否一定要变(否)

        结合真实世界,很明显这两个答案又是否,用户和vip是两个不同的事物,用户要做的是用户注册,变更注销等,vip要做的是折扣,福利,特权。而付款与vip会员的关系呢?

  • 付款发生变更时,vip会员是否一定要变(否)
  • vip会员发生变更时,付款是否一定要变(否)

        付款和vip之间也是两个不同的事物,之间也是调用关系,在付款中调用会员折扣,福利和特权。那么vip自然也就是独立出来的一个领域模型

         同理如果增加支付方式,我们也要分析扣款和支付方式之间的关系

  • 付款发生变更时,支付方式是否一定要变(否)
  • 支付方式发生变更时,付款是否一定要变(否)

        自然也是独立出来的两个实体和现实世界的场景,我们一样可以设计

         因为是要和外部系统进行对接,为了避免外部系统对我们的影响最小化,我们可以加入适配器模式,订单Service在进行支付的时候,不再是一个一个外部的支付接口,而是支付方式接口,与外部系统解耦保证支付方式接口是稳定的,那么订单Service接口就是稳定的,比如支付宝支付接口发生变更时,影响的只限于支付宝的Adapter,当要增加一个新的支付方式就增加一个Adapter就可以,日后修改范围就缩小了。

         讲到这里,是不是对遗留系统的问题来深刻领会到【领域驱动】的核心思想?

4、转型利器微服务

        上面曾说过,微服务是业务模型的系统落地,微服务本身不是一项技术,而是对业务模型的系统实际解决方案,可是微服务不是银弹,也有很多坑。当按照模块拆分的时候,每次变更都需要修改很多的微服务,不但多个团队都要变更,还要同时打包同时升级,没有降低维护成本,反而使得发布比过去更麻烦,那不如不用微服务?那是微服务不好吗,我又一度陷入了沉思,这个时候我去翻阅一些资料的时候,Martin Flower定义微服务时提到的“小而专”,我们Get到了“小”,缺忽略了“专”。这里的专就是要小团队专业维护,就是尽量让每次的需求变更,交给小团队独立完成,让需求变更落到某个微服务变更,这样才可以发挥微服务的优势。

        经过深入想后才发现,微服务的设计真的不仅仅是一个技术架构更进的事,而是对原有设计提出了更高的要求,即服务内高内聚,服务间低耦合,而我们没有过硬的能力和设计经验,就不能把微服务带来的复杂度归咎于微服务的弊相关。只能说我们没有更好的理解到微服务核心给我们解决了什么问题。那么谁来拯救微服务现在的情况?

        同样还是DDD,我们转型微服务的重要根源,就是系统的复杂性,即系统规模越来愈大,维护越来越困难,才需要拆分微服务,然而拆分每个微服务不代表都各自运行,而是彼此协同组织在一起,而DDD恰恰就是帮助我们组织微服务的实现方法。

        要让DDD在团队中用的好,需要一个支持DDD与微服务的技术中台!有了技术中台,开发团队就有更多的精力放在对用户业务的理解,对业务痛点的理解快速开发交付,这样不仅编写代码门槛减少了技术降低了,还使得日后变更更加容易。

        那么DDD具体如何解决微服务拆分难题呢?在下一章节我会深入讲述【领域模型】的众多名词,概念比如限界上下文,聚合,仓库,工厂等,这些名词告诉我们【领域模型】会将一个系统划分成多个子域,每个子域都是一个一个独立业务场景;同时每个子域实现的就是“限界上下文”等等类似这样的抽象世界。

5、技术中台又是什么

        在以往建设的系统中,基本都分为前台和后台,前台一般指用户交互的UI界面,后台是指服务端完成业务逻辑的操作。然而在开发很多业务系统中,有一些内容是共用的部分,甚至在未来的开发系统中也会使用到,所以如果我们能把这些内容提取出来做成公用组件,那未来开发系统就简单了。不用每次重复开发,直接复用这些组件就可以。那么这些复用的组件到底属于前台还是后台呢?其实都不属于,既包含前台的界面,也包含后台的逻辑。这就是中台的概念!

        中台:将以往业务系统中可以复用的前台与后台代码剥离个性,提取共性,形成公用组件。

        所以阿里提出了小前台,大中台的战略得到了业界普遍的认可,从分类上看,中台分为业务中台,技术中台,数据中台;

         那么清楚这些概念后,自然就会知道DDD与微服务底层技术进行封装,从而支持开发团队在未来实现快速交付,以应对激烈竞争的市场,所以首先要清楚实现快速交付的技术特点,才能清楚这个技术中台如何建设。

        我们大多数公司的团队是这样的,从需求交付的整个过程,要经历多个部门的交互,才能完成最终交付,大量的时间被耗费在了部门之间的沟通协调中, 这样的团队叫做烟囱式开发团队。 

         这样的开发团队会导致烟囱式的软件开发,每个功能都要设计大量的controller,service,dao需要编写大量的代码,很多是重复的,量越大bug越多,变更困难。

         统一的发布制约了交付速度,对于业务负责人把需求交给多个团队开发时,A团队发布的可能需要1周就可以完成,但是A团队完成后无法立即交付,而B团队要开发两周,必须要等B开发完成后再一起交付给客户。即便开发速度再快,不能立即交付,就无法带来价值

         如何解决这个问题?将桶装的烟囱横着来,将所有的需求,ui,数据库,运维等工作都交给这些模块单独的特性团队,所有与购物相关的功能都是这个团队来维护,每个这样的团队负责整个模块的交付过程,不再需要等待其他团队每个功能都要一起发布,这就是特性团队。而这样的团队成本是非常高的,几乎每个成员都是全栈工程师。

         通过对技术的选型,构建技术中台比如UI,应用,数据库进行了封装,然后以API接口的方式开放给上层业务。这就是大前端,是一种职能的转变,不是我们业界说的“前端”,而是业务人员更加关注业务,深刻理解业务并快速应对市场需求的变化。架构团队从业务开发角度进行提炼共性,保留个性,将这些共性沉淀到技术中台中。

        如何打造一个强大的技术中台呢?在Martin Flower《企业应用架构模式》中指出一个概念,叫命令与查询职责分离模式,命令就是增删改操作,其中指出所有命令的增删改操作,应当采用【领域驱动】设计思想进行软件设计,所有查询的功能应当采用事务脚本模式,即直接通过SQL语句进行查询。

        技术中台的实现只存在统一的一个Controller和单个Dao,和以往不同的是,每个功能有各自的前端UI,调用后台的逻辑时不再调用各自的Controller,而是统一调用一个Controller,每个功能的前端调用这个Controller传递的参数是不一样的,将Service和Dao装配起来后,形成一个一个的bean,前端只知道调用的是bean,并不需要知道调用的是哪个Service,这样的设计既保证了安全性,实现前后端分离,也实现解耦。紧接着前端还要传递一个Method,即调用的是哪个方法和哪个json对象,这样Controller就可以通过反射进行相应的操作,通过规范和契约,我们认为前端开发人员已经知道调用后端的哪个bean,哪个method,以及什么格式的json。

         举个例子:前端统一只访问ORMController,访问localhost:9003/orm/{bean}/method,bean是Spring的bean的id,method是bean中需要调用的方法(此处不支持方法的重写,如果是重写,则会调用同名方法中的最后一个),如果调用的方法有值对象,必须将值对象放在方法的第一个参数上;如果调用的方法既有值对象,又有其他参数,则值对象中的属性与其他参数都放在该json对象中。如果要进行权限校验,需要在ORMController之前做,建议在网关或者filter校验。

         具体的流程是

         那么单Dao怎么设计,比如下面的图,将每个值对象对应的表通过xml的方式进行对应,并最终完成数据库的持久化操作。值对象中有很多属性变量,最终只有需要持久化的属性变量才需要配置。

         有了以上的设计,每个Service在Spring中都是统一注入BasicDao,如果要使用DDD的功能支持,注入通用仓库Repository,如果要使用Redis缓存,注入Redis相关的Repository就可以。这里要注意,基于这类技术中台框架的业务开发,包括Spring配置,Mybatis配置,vObj的配置建议都采用XML文件的形式,不要采用注解。

        单Dao设计流程:

  • 单Dao调用vObjFactory.getVObj(class)获取配置信息vObj
  • 根据vObj.getTable()获得对应的表名
  • for循环拿到vObj.getProperties().getColumn()获得值对象对应的字段,反射拿到具体字段对应到的值,拼接成sql
  • 进行数据库操作

        接下来看下查询功能的技术中台设计

        与增删改不同的是,Service只有一个,往Service中注入的是不同的Dao,可以装配成不同的bean,查询部分不需要传递method参数,这样前端查询的时候传递不同的json参数就可以实现不同的查询。

         比如进行查询的时候,前端输入localhost:9003/query/{bean},如下图

         前端个Service查询交互如下图

         所有的查询都是一个Service,注入了不同的Dao,配置成不同的Bean完成不同的查询,每个Dao可以通过Mybatis框架注入,配置不同的mapper完成不同的查询,然后将其注入Spring中完成配置。

         对于Service和Dao的交互如下

         但是在某些业务中需要个性的在查询前进行某些处理,现在设计中只有一个Service,怎么办呢?

        我们提个概念,叫钩子Hook,在QueryService中设计成空方法,因此调用他们跟没有调用是一样的,如果需要在查询前后添加某些处理,则编写一个QueryService的子类并重写这两个方法,只要在Spring配置中配置这个子类即可。

         通过以上中台设计,会简化很多,设计一个普通的查询,只需要制作一个Mybatis的查询语句配置,在Spring中配置一个bean,就可以通过前端进行查询,甚至不需要新增class,除非在查询前后要做些什么。

        本章可能最后一个章节比较抽象,后续我会拿实战案例Demo来演示技术中台是如何落地。初探了DDD,理论一定要具备,才有更好的实践。

         下一章节,走进DDD如何指导架构和业务设计的。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>