討論交流
我的两分钱 2022-09-02 121 0 0 0 0
程序开发,设计模式,如果说『设计模式』能指导我们写出好的代码,那么DDD就能指导我们设计出好的系统架构,业界在过去接近20年的时间里,一再地证明了DDD的价值。关于为什么需要DDD,常见的介绍往往是先介绍什么是DDD,再…
如果说『设计模式』能指导我们写出好的代码,那么DDD就能指导我们设计出好的系统架构,业界在过去接近20年的时间里,一再地证明了DDD的价值。关于为什么需要DDD,常见的介绍往往是先介绍什么是DDD,再介绍DDD能解决什么问题,最后得出我们需要DDD的结论。今天,作为一个DDD的实践者,我想从大多数研发团队都面对的问题、问题的原因、问题的解法这样来倒推出我们需要DDD的原因。
对于所有的研发团队,都有一个基本的目标,那就是:
        高效率、高质量地持续交付
『怎么又不能按期上线啊?』『怎么线上又出问题了?』,看到这两个来自业务团队最常见的抱怨,我想每个技术Leader都会苦笑。
在不同的公司、不同的团队,下面这样的故事总是不断重演。
刚开始,业务还很小、团队也不大,整个团队都充满了的激情,每个人都是高产的小能手,常常是产品团队早上提出一个需求,两三个研发人员快速讨论一下,晚上就能上线,产品也因此快速迭代,业务也越做越好。
慢慢地,用户越来越多、产品变得越来越复杂,产品和技术团队的人也越来越多,并逐渐分化出了专门负责不同方向的团队。一个业务流程的变更需要横跨不同的团队,一个需求的实现需要修改多个模块的代码,每次上线前也需要非常谨慎地做好测试,但即使如此,还是时不时地有用户反馈一些Bug,整个团队的交付速度,虽然还可以接受,但相比项目的早期已经大大下降了。
随着业务的进一步成功,产品已经复杂到整个公司/业务部门没有几个人能完全说清楚所有的业务规则了,而研发团队的代码也迅速地膨胀到几十万甚至上百万行,成百上千的模块里分布着几十上百的业务规则,此外还有数不清的技术设计和实现。每一个小小的需求,都需要召集至少两三个不同团队的产品经理、五六个研发团队的开发和测试人员,开好几次几个小时的会才能把到底谁要做什么说清楚。然后研发团队实现需求的时候发现又有更多的团队需要协调,经过几周的加班,痛苦又来到了测试团队,虽然是一个小需求,却不得不把整个系统测一遍,每次都能发现一些莫名其妙的bug,每次修复后又需要再做一遍完整的回归测试才放心,两三轮挣扎后,终于到了要上线的那个夜晚。产品、开发、测试吃过晚饭,祈祷上天保佑后开始上线,好多个团队聚在一起,按照事先精密计算的顺序,轮番登场上线自己负责的部分,终于天快亮的时候,上线完成了,大家最后做了一遍线上检查后疲惫地回家了。之后的几天线上又不断地发现一些小bug,大概一周后终于所有的bug都消灭了,最初的需求终于上线了。
但这仅仅是噩梦的开始,逐渐地,每增加一个小小的需求,都会像雪崩一样发现无数的关联的地方,团队80%甚至更多的时间都花在这些关联的修改上,花在莫名其妙的各种bug上,每次计划的上线总是会被各种奇怪的原因延期,每次修改后都会有大量的bug被爆出来,老板不得已增加的人手,要花几个月的时间才敢下手改代码,而他们的修改又让系统更加的复杂和难以修改。
最后,系统变成了一个混沌的大泥球,任何新的需求都变成了薛定谔的猫。痛苦了无数个夜晚后,大家决定重写一个新系统来替代老系统,所有人都长长地舒了一口气,但,这只是同样剧情的开始。
为什么所有的人都很努力,为什么大家想了那么多办法,最后却还是这样的结果?
从上面的描述,你能看到问题的根本就来自于我们所面对的复杂性,而这里的复杂性有两类:
  • 业务内生的复杂性

  • 多人、多团队配合增加的额外复杂性

关于业务内生的复杂性,我想讲一个我亲身经历的故事(我用XYZ之类的字母来代替一些敏感的信息)。
我曾经负责的一个业务,其目标是在X这个业务方向,为全国上百万的商家提供一套管理他们日常业务的IT系统,我们需要提供从软件到硬件的全套解决方案。而这上百万的商家按照其业务规模、服务范围、经营模式、地域差异又可以细分为10多个不同的子业态。所以你能看出来,这是一个比较复杂的业务,而在这个业务方向上,我们是后来者,之前全国成规模的厂商有几十家,要迅速地打开局面,除了自研,我们还采取了并购的方法来补充我们的产品线。具体而言,我们前后收购了A和B两家公司,这两家公司都是架构在XX云上的,出于数据安全以及一些竞争方面的考虑,我们计划把A和B的产品从XX云上迁移到我们自己的云平台上。
我们面对的一个挑战是,A和B公司的系统都大量地应用了XX云提供的一些PaaS服务(如存储、服务发现等),由于A和B都没有事先考虑平台绑定的问题,所以这些PaaS服务和系统深度地耦合在了一起,要把A和B迁移到我们自己的云平台上,就需要用我们自己的PaaS服务来替换掉XX云的PaaS服务,这就涉及了大量的代码改动,而A和B的系统已经有了大量的客户,我们必须要保证这个迁移过程对客户是无感的。
这个迁移必须还是由A、B团队主力完成,因为他们是对系统最熟悉的人。对比A、B的系统,A比B功能略多,但从产品的复杂性而言,两家的系统都在同一量级。A、B团队的情况是:A团队在北京,研发团队有几百人,其中不乏来自百度、360等大厂的大牛;而B团队在深圳,研发团队只有几十人,来自大厂的牛人不多。
如果此时请你猜测一下,A、B两个团队谁能成功完成迁移,我想一般人都会觉得A的成功概率要大一些。然而,最后实际的结果是B成功地完成了迁移,而A经过分析,最后的结论是根本无法迁移
为什么A团队技术看起来技术更强一些,最后却失败了呢?我们当时也review了两个团队的代码,A团队的质量看起来还要更好一点,为什么最后B团队却成功了呢?
对复杂问题,最常见也是最有效的一个策略就是分而治之,我们采取的策略就是逐个子系统迁移,这样不但可以简化问题,也可以规避风险。然而就是在这个策略下,我们发现在A、B在业务早期的一个产品决策,直接导致了上面的所说的结果。
由于A的技术团队相对较强,人也多,所以A公司在起步的时候,一上来业务策略就规划了多个业务系统(如门店、CRM、ERP等),这些业务系统整合在一起形成一个解决方案,全方位满足客户需求。但由于整体解决方案这个定位,虽然A也划分了若干个系统,但实际上,系统间并没有对系统边界过于强调,经过几年的开发,系统的功能模块间形成了大量的耦合。同样糟糕的情况也发生在产品团队,由于缺乏良好的规划,一个业务能力往往分布在多个系统中(比如,对账能力就跨越了订单、支付、财务等若干模块),最终在我们计划迁移的时候,A的情况如下图:
而B公司则走了另一条路,因为他们人少也缺技术大神,所以他们的业务策略是先集中精力做好一个系统,然后再去做下一个系统,这些系统既能单独售卖,也能整合在一起形成一个解决方案,在我们计划迁移时,B的情况如下:


我想把这两个图放在一起,为什么B能够成功迁移就很明显了。
为了进一步说明问题,我们还可以做一个粗略的量化分析:假设A和B都有m个业务系统,每个业务系统中都有n个功能模块。
  • 对于A来说,最终几乎所有的功能模块都耦合在了一起,所以其系统复杂度为O((m*n)^2)

  • 对于B来说,我们我们也假设其每个子系统内所有功能模块是全耦合的,且各个子系统之间也是全耦合的,那么其复杂度为O(n^2)*m + O(m^2)
  • 可以做个具体计算,假设m = 10, n = 10, 则A的复杂度为10,000, 而B的复杂度为1,100,A的复杂度几乎比B高一个数量级

上面的分析只是从迁移的角度出发,仅仅计算了系统层面的复杂度,实际在日常的业务开发中,由于A的业务能力分散在多个系统中,几乎每一个业务需求的改动都要涉及若干个系统,所以A的情况还要更糟糕一些。
对于A、B两家公司,他们的业务决策本身,我觉得都很合理,都是从自身实际情况出发很务实的决定,A和B在市场上也很成功。那么A的产品技术团队到底做错了什么呢?毕竟实际上A的代码质量还要更好一些,A的团队能力也要更强一些。
我认为A在整个产研过程中,遗漏了关键的一步,那就是业务架构的设计。这个业务架构,不是我们在各种PPT上见到的那种上下分几层,每层放几个模块但实际毫无用处的架构图。
这个业务架构首先是和技术几乎完全无关的,它是完全围绕业务展开的。它需要回到问题域,去回答我们到底要服务哪些客户?我们应该通过哪些业务能力来服务这些客户?我们的问题域应该如何划分?
这个业务架构需要对用户海量的需求进行抽象和分类,要对每一个业务能力到底属于哪个问题域仔细斟酌,比如,发票到底是订单属于订单域还是财务域?或者应该自己就是一个独立的问题子域?
这个业务架构需要对业务流程的每个环节都从用户角度设身处地地思考,比如对于一个餐饮企业来说,菜品管理到底是服务员的关注点还是老板的关注点?
这个业务架构需要对问题域中的每个关键词咬文嚼字,比如『』对订单域、对安全域、对客服域到底有什么不同的含义?是应该有一个统一的用户定义还是每个域都应该有自己的定义?
这个业务架构需要明确定义清楚每个问题域的边界是什么,问题域之间应该如何交互,哪些信息是只和问题域自己相关的,哪些是跨问题域的。
这个业务架构需要与时俱进,它不是review后就被束之高阁的PPT,它需要随着我们对业务的理解不断深入,而不断被更新的。
所以,说了这么多,到底和DDD有什么关系呢?其实熟悉DDD的同学应该已经看出来了,这里实际就是在讲DDD里的domain,subdomain和bounded context,domain,subdomain是在讲问题域,而bounded context则是domain,subdomain在解决方案域的对应。
DDD讲了很多内容,战术部分包括像是实体对象、聚合根、值对象、领域服务、应用服务、工厂、资源库等内容,这也是大多数DDD实践者最关注的,但我认为DDD价值最高的是其战略部分,主要就是两块:界限上下文(Bounded Context) 和统一语言(Ubiquitous Language)。为什么这两者是最有价值的部分?回到上面对A公司的分析,A团队有技术大牛,代码质量也更好,这些相当于是在DDD的战术部分做得很好,但是由于A忽略了战略部分,没有做好和坚持系统边界的划分,最终项目失败。而B从能力、资源各个方面都弱于A,但是幸运的是,B由于其老板对业务的深刻理解,他的业务决策相当于帮B的产品技术团队做了DDD的战略部分,所以B最终以弱胜强。
从这里也可以看到,DDD战略部分要做好,一定是来自对业务的深刻理解。理解业务需要花费很多时间精力,但相比后面若干年在产品技术上的投入,在业务架构部分所做的投入,是杠杆率最高的架构设计。
上面的分析,重点谈了界限上下文,最后再来介绍一下统一语言。
统一语言的意思是,在产品、技术工作的每个环节都要围绕业务来展开,都要用业务语言来描述自己的需求、架构、服务、代码;这个描述是没有歧义的,任何模棱两可的地方可能都意味着我们对业务理解的不透彻;这个语言是有边界的,我们不追求一个跨越所有问题域的语言,但是在每个界限上下文内,这个语言是清晰没有二义性的;这个语言是业务、产品、技术团队在交流中一起创造的;
统一语言也许没法像上面分析A、B那样来定量分析,但定性分析的话,它至少有如下这些好处:
  • 统一语言是一家公司最宝贵的资产一家公司的团队的人可能来了又走业务系统的实现会不断地用新技术取代旧技术,但只要公司的业务方向不变,那么最终沉淀下来的就是统一语言,这是一家公司的核心竞争力


  • 统一语言能帮我们理解业务:实际上统一语言和上面介绍的界限上下文本身就是一体两面,统一语言是我们对业务的理解,而界限上下文是业务理解在物理世界的映射。比如当你发现『用户』在安全、交易、客服等问题域的含义完全不同的时候,你就应该想到至少应该划分出安全、交易、客服这三个完全不同的子域。反过来,如果你不这么做,用一个用户模型来处理上述三个问题的时候,你就引入了不必要的耦合,对用户的一个修改会带来在多个系统中的工作
  • 统一语言能帮我们发现业务上的盲点:比如我曾经经历过的一个项目,公司想统计一下到底有多少商户在和我们合作。听起来需求很清晰明确,但是实际讨论的时候,你就会发现不同的业务对商户的定义是完全不同的,有的业务是从合同视角来看的,和多少个不同的主体(可以简单理解为公章)签了合同就有多少个商户;有的业务是从门店视角来看的,有多少个和我们合作的门店就有多少个商户;有的业务是从营业执照来看的,有多少个唯一的营业执照编号就有多少个商户;有的业务是从财务角度看的,有多少个需要我们打款的商家银行账户就有多少个商户;还有些业务场景本身就是模糊的,比如有些业务是多个商贩租借使用同一个营业执照,比如有些场景是店中店等

  • 统一语言让我们的代码更可维护:比如从技术角度出发,你可能会设计一个API,类似 Order.update(...), 从统一语言角度,这个API可能是Order.addItem(...), Order.changeItemAmount(...),显然后者的表达性更强,而更强的表达性则意味着你的代码更好理解,更好维护

  • 统一语言让我们的沟通更高效:实际工作中,最常见的场景是业务和技术团队互相听不懂对方在说什么,于是出现了业务<->产品<->技术这样一个翻译链,而信息则在链条中丢失和扭曲,当我们使用统一语言的时候,这种翻译就再也不需要了,实际上,如果我们充分使用统一语言,我们的系统API,即使是让完全不懂技术的销售来看,他也能完全看懂是什么意思

统一语言的好处,如果继续列举其好处,其实还可以写很多,但我想,也许你亲自去看一看DDD,甚至在项目中实践一下DDD,尤其是DDD的战略部分,一定会更有体会。
所以为什么我们需要DDD?
总结一下就是,因为日益增长的复杂性导致我们的研发效率不断下降,最终严重影响业务的成功;为了应对复杂性,我们采取分而治之的策略,把一个复杂的问题分解为若干个相对小的问题;进行这种问题分解的一个有效工具,就是通过业务架构设计而明确定义界限上下文,这可以帮助我们成数量级地简化复杂性;和界限上下文一体两面的是通用语言,通用语言是一家公司最宝贵的财产,它能从多个方面来帮我们提升效率,帮助业务成功。

Tag: 程序开发 设计模式
歡迎評論
未登錄,
請先 [ 註冊 ] or [ 登錄 ]
(一分鍾即可完成註冊!)
返回首頁     ·   返回[討論交流]   ·   返回頂部