作者简介:胡正军,御家汇架构团队负责人,坐标湖南长沙。开发,运维,测试,大数据均有负责,杂而不精。乐于复杂系统重构,拥抱DDD
上一篇文章说了小团队也能做中台,里面提到我们用了领域驱动设计(DDD)这个方法论来开发中台,本文对我们为什么使用DDD做一个分享,下一篇再讲我们怎么实践的DDD。
DDD出现得很早,2004年Eric Evans就写了《领域驱动设计:软件核心复杂性应对之道》,但使用并不广泛,拿长沙这边来说,使用的企业可以说是寥寥无几。
近几年DDD随着微服务反而火了一把,各种技术群里一言不合就DDD,也有一些在线培训和书籍出版,比如人保的架构师欧创新就在极客时间开了专栏,预计本月还会出版一本书籍。然而大家虽然谈论得多,真正落地的少。
< 1 >系统不复杂,没必要用DDD;
< 2 > DDD概念多,理解难,懒得去学;
< 3 > 就算一个人学了也没用,需要团队都用起来,由于人性本懒惰,DDD对业务和开发都要求有点高,所以不想做,还是CRUD爽点。
DDD是为了解决复杂业务系统构建的一套方法论,有战略设计和战*术设计两大块。战略设计的核心是统一语言和限界上下文,战*术设计的核心是独立领域逻辑层。复杂业务系统为什么要用DDD,可以从3个维度来说明。这3个维度分别是软件开发过程,复杂系统设计,模块编码。可以看到这3个维度是一级一级降低维度的,软件开发过程涉及多个岗位(产品经理,开发,测试,业务运维),属于开发流程这个维度,复杂系统设计涉及的岗位是架构师或者开发,属于架构设计这个维度,模块编码涉及的岗位主要是开发了,属于代码编写这个维度。我们先分析这3个维度上有什么问题,再看DDD都能提供什么指导。
一个简单系统的开发是不需要分这么多岗位的,大学期间做的一些兼职小项目就是一个人把需求,开发,测试,运维全做了,效率很高,如果说搞多个岗位多个人来做,反而效率很低。不过这种情况随着项目复杂度上升就不一样了,一个人是没法搞定所有事情的,于是我们设立了各种岗位,通过团队分工让专业的人做专业的事情。拿开发这个岗位来说还分了前端和后台开发,也是因为技术复杂性的要求,必须分工。一个理论是建立知识壁垒并能有效交换能提高效率,这个方面详细的论述可以看下八叉说的视频。通过专业分工来提升效率在生活中有很多地方都有体现,比如流水线作业什么的,这在国富论和科学管理中也有提到,这个方面详细的论述可以看下八叉说的视频。
这里得到个结论:
软件开发过程中的技术知识通过分工建立知识壁垒来提升效率
但是软件开发还有一类知识是没法通过分工解决了,上图箭头表示的就是领域知识必须随岗位传递,需求分析师把业务转换成需求文档,然后这个需求文档里面的业务知识必须传递给开发,开发必须理解清楚了才能干活,现实中确实出现了很多这样的情况,开发需求都没理解透,就开始设计编码,后做出的东西跟实际业务需求不一样,返工,开发和产品经理矛盾重重。如何让业务知识快速传递,是迫切需要解决的问题。
这里得到第二个结论:
软件开发过程中的业务知识需要消除知识壁垒才能提升效率
从两个结论看好像有冲突,从个结论看,分工是有意义的,从第二结论看,不分工反而是好的。随着各个岗位专业度的提高,分工是必然的,那么怎么解决业务知识传递问题, DDD的一个核心模式是统一语言,用于解决解决业务知识传递的问题。
1、统一交流语言。我们把业务名词的含义事先确定好,在业务和技术的各个岗位都统一认知,这样在交流时,由于对业务名词的理解已经同频了,会节省很多沟通成本。这里举个没统一语言的例子,我们公司有个有个渠道的概念,每次讨论这个渠道就容易出现每个人理解不一样,有的把电商平台一个店铺当渠道,有的把电商平台当渠道,还有人理解成其他含义。这是因为每个公司都有些历史遗留原因对名词的定义变味道了。2、统一领域模型和代码模型。我们知道一个系统重要的知识沉淀就是代码和文档了,当然很多公司都没文档,程序员嘛,都一个尿性,恨别人不写文档,恨自己写文档。想一想当一个新人来接手一个项目,这个项目有一份设计文档和代码,设计文档里面描述的业务模型跟代码模型不一样时,我想这个新人是蒙圈的,到底哪个是对的,心里会骂娘。当业务模型和代码模型一样,会大大降低我们系统的认知复杂度。
二、 复杂系统设计
为了降低业务系统复杂性,一般是通过分而治之的方式,现在流行的微服务就是基于业务维度进行拆分,那么到底怎么拆分。如下图,每个点代表一个领域对象,当很多个对象混合在一起时,设计者或者开发者需要了解所有领域对象,无疑增加了复杂度。如果能找到一种方式合理划分边界,让联系紧密的对象聚合在一起(比如下图中形成ABC三个聚合),聚合之间通过接口*交互,那么接口数量会是少的。有点类似K聚类算法,总能找到一种优的划分方法,当然很多情况下依赖架构设计者的经验。DDD的限界上下文提供了一种指导思路,限界上下文划分好后,一个限界上下文可以对应一个微服务。这里同样运用了建立知识壁垒可以提升效率这个理论,开发A服务的人实际上不需要了解B服务的内部实行,只需要了解B提供的接口即可,A服务的领域对象数量已经大大下降了,无疑降低了复杂度。
拿一个大学里面的一个学生管理系统来说,涉及到如下领域对象:学生,教师,角色,权限,菜单,课程,课程排班,签到等。现在来划分限界上下文,因为学生和教师都需要登录和权限,我们一种上下文分解的方法是鉴权上下文<角色,权限,菜单,学生,教师>, 授课上下文<课程,课程排班,签到>。由于DDD里面规定限界上下文一个领域对象有确定的含义,学生和教师这个两个在鉴权上下文里面是系统的一个用户,放到授课上下文里面代表了两种不同的身份,也就是当我们说到学生时,到底是需要登录的一个用户还是授课里面的学生,含义是不确定的,所以这里缺了一个领域对象用户。增加用户这个领域对象后,限界上下文重新划分,只需要2个接口做交互。
三、模块编码
传统的三层结构是web,service,dao,这个大伙都很熟悉,一个业务流程稍微复杂点会导致service里面的代码变成大泥球,我在上一份工作中,见过一个service类达到上万行,维护及其痛苦,怎么痛苦法呢,就是每次转测试,都会产生20多个bug,改完又会出现新的,一轮一轮无法收敛。我实在受不了,就跟项目经理申请重构,项目经理是不同意的,没办法只能立军令状,就开干了,终一万多行代码变成4000行左右,bug终于收敛。解决service层大泥球的一个方法是分离复杂性,把service分为应用层和领域层,这块内容可以参考王林在infoq的文章(https://www.infoq.cn/article/A3cgSUWuRulXHl2c_dUr),文章中提到了一个架构原则是业务和技术分离。领域层放业务逻辑,应用层放技术逻辑,复杂性降低,并且领域层更便于测试。
DDD战*术设计就是把业务逻辑放到核心位置,整洁架构和六边形架构也是如此,跟业务相关的代码写到实体,值对象和领域服务里面,一些事务,缓存等技术相关代码写到应用服务里面。举例说明:购物车有个操作是添加商品到购物车,应用层ShoppingCartApplicationService把事务,缓存处理掉,核心业务逻辑在ShoppingCart这个领域对象里面。
应用层代码如下:
四、总结
DDD的统一语言在软件开发过程中能帮助业务知识更好的传递,限界上下文能指导复杂系统的拆分,战*术设计能帮助代码更好的分层。有的公司虽然没有用DDD这么一套组合拳,可能已经在利用这些好的方法来开发软件,其实DDD提供一套方法论,也不是必须全部用上,完全可以只借鉴其中一部分。比如DDD的统一语言是通过降低业务知识的传递提升效率,可以应用到实际开发中。举一个实际的例子,很多企业内部系统也采用了前后端分离,然后用前端开发和后台开发两拨人分别开发,再接口联调,联调成本很大,完全可以换一种方式,让后台开发来做前端,节省联调成本。那这里可能有人有疑问,后台开发做前端会不会效率很低?其实只要让前端提供了架子和前端组件,再辅助一些组件使用文档和代码示例,后台开发完全可以很快上手,而节省的联调成本会很可观。后想说一句,方法论能提供指导,但也不要生搬硬套,需要根据自身情况进行裁剪,灵活运用。—————————— END ——————————