DDD应对复杂

复杂

Eric Evans所著副标题–Tackling Complexity in the Heart of Software,对于简单系统其实没有必要使用DDD,只有在复杂系统中,才能体现DDD的价值

那么何为“复杂”,或者说为什么软件是复杂的呢?

即使是研究复杂系统的专家,如《复杂》一书的作者 Melanie Mitchell,都认为复杂没有一个明确得到公认的定义。不过,Melanie Mitchell 在接受 Ubiquity 杂志专访时,还是“勉为其难”地给出了一个通俗的复杂系统定义:由大量相互作用的部分组成的系统,与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通讯,并且组成部分的相互作用导致了复杂行为。

这个定义同样可以表达软件复杂度的特征。定义中的组成部分对于软件系统来说,就是我所谓的“设计单元”,基于粒度的不同可以是函数、对象、模块、组件和服务。这些设计单元相对简单,然而彼此之间的相互作用却导致了软件系统的复杂行为。

《管理3.0:培养和提升敏捷领导力》中从两个维度分析复杂:行为(预测能力)和结构(理解能力),分别举了实际生活中简单的例子

理解力维度分为 Simple 与 Comlicated 两个层次,预测能力维度则分为 Ordered、Complex 与 Chaotic 三个层次

在英文中,表示复杂的有Complicated和Complex

Simple = easily knowable.

Complicated = not simple, but still knowable.

Complex = not fully knowable, but reasonably predictable.

Chaotic = neither knowable nor predictable.

参考复杂的含义,Complicated 与 Simple(简单)相对,意指非常难以理解,而 Complex 则介于 Ordered(有序的)与 Chaotic(混沌的)之间,认为在某种程度上可以预测,但会有很多出乎意料的事情发生

针对复杂的定义,分析角度以及应对的方法,绘制一张表格:

























角度 维度 表象 应对
理解力 规模

软件的需求决定了系统的规模。当需求呈现线性增长的趋势时,为了实现这些功能,软件规模也会以近似的速度增长

函数存在副作用,调用时可能对函数的结果作了隐含的假设;


类的职责繁多,不敢轻易修改,因为不知这种变化会影响到哪些模块;


热点代码被频繁变更,职责被包裹了一层又一层,没有清晰的边界;


在系统某个角落,隐藏着伺机而动的bug,当诱发条件具备时,则会让整条调用链瘫痪


不同的业务场景包含了不同的例外场景,每种例外场景的处理方式都各不相同;


同步处理与异步处理代码纠缠在一起,不可预知程序执行的顺序
分而治之,控制规模
结构


结构之所以变得复杂,在多数情况下还是因为系统的质量属性决定的

代码没有显而易见的进入系统中的路径;


不存在一致性、不存在风格、也没有统一的概念能够将不同的部分组织在一起;


系统中的控制流让人觉得不舒服,无法预测;


系统中有太多的“坏味道”,整个代码库散发着腐烂的气味儿


数据很少放在使用它的地方,经常引入额外的巴罗克式缓存层,目的是试图让数据停留在更方便的地方
保持结构的清晰与一致

所有设计质量高的软件系统都有相同的特征,就是拥有清晰直观且易于理解的结构。
预测能力 变化 在设计软件系统时,变化让我们患得患失,不知道如何把握系统设计的度。若拒绝对变化做出理智的预测,系统的设计会变得僵化,一旦变化发生,修改的成本会非常的大;若过于看重变化产生的影响,渴望涵盖一切变化的可能,一旦预期的变化不曾发生,我们之前为变化付出的成本就再也补偿不回来了。这就是所谓的“过度设计” 拥抱变化

可进化性(Evolvability)

可扩展性(Extensibility)

可定制性(Customizability)

根据上面的总结,想起《架构与架构师》中提到的架构师需要面对应用程序的两个层面需求:功能需求与非功能需求,功能性需求越多越大那对应的软件规模增长越快,可变性越大;非功能需求就要考虑质量属性,也就是整体结构要清晰,明确的规范约束,易于理解好维护。而这两个层面需求正好又对应着业务复杂度与技术复杂度

技术复杂度与业务复杂度并非完全独立,二者混合在一起产生的化合作用更让系统的复杂度变得不可预期,难以掌控。同时,技术的变化维度与业务的变化维度并不相同,产生变化的原因也不一致,倘若未能很好地界定二者之间的关系,系统架构缺乏清晰边界,会变得难以梳理。复杂度一旦增加,团队规模也将随之扩大,再揉以严峻的交付周期、人员流动等诸多因素,就好似将各种不稳定的易燃易爆气体混合在一个不可逃逸的密闭容器中一般,随时都可能爆炸:

DDD应对

这让我回想起了“结构化思维”,逻辑+套路

逻辑是一种能力,而套路是方法论,是经验。逻辑是道,方法论是术;逻辑可以学习很多思维模型理论,但套路有路径依赖,这也是去大厂的好处,可以接触到各种大牛,直接获取他们的经验和方法

DDD是如何应对这些复杂性的呢?

  1. 限界上下文分而治之,边界划分后,各子域相对整体规模就小了
  2. 结构方面通过技术角度:分层架构、六边形架构…分离技术实现与业务逻辑
  3. 变化,不再过程式编程,加强抽象领域模型,通过过程分解+对象模型,拆解并业务显示化,达到更好的复用性和易理解性

banq提出类似总结解决复杂性两种方法:拆解成松耦合的组件+使用容易让人明白的套路表达出来

拆解也就是引入了“领域或子域”以及“有界上下文”划分边界;再引入各种模式名词,比如聚合、实体、值对象、工厂、仓储、领域事件,让知晓这些模式的人能够一下子定位到功能对应实现的组件,随着人们逐步了解,对于理解DDD系统来说,就不再是复杂的

开发流程

我们在开发、运营、维护软件的过程中很多技术、做法、习惯和思想。软件工程把这些相关的技术和过程统一到一个体系中,叫“软件开发流程”,软件开发流程的目的是为了提高软件开发、运营和维护的效率,以及提升用户满意度、软件的可靠性和可维护性

发展了很多的流程,这儿复习一下最资深的瀑布模型

瀑布模型

瀑布模型(Waterfall Model)1970年温斯顿·罗伊斯(Winston Royce)提出了著名的“瀑布模型”,直到80年代早期,它一直是唯一被广泛采用的软件开发模型

当软件行业还在年幼时期,它从别的成熟行业(硬件设计,建筑工程)借用了不少经验和模型。在那些“硬”的行业中,产品大多数遵循[分析->设计->实现(制造)->销售->维护]这个流程

软件系统很难做到像建筑行业一样,程序员只要根据图样一步步实施就能完成项目。没人可以完整设计出一个大型项目的图样,这是瀑布软件工程方法的致命问题

比如A拥有一家跑车公司,可以给客户自定义生产跑车。有一天一土豪来到A的公司,跟A商谈了一个跑车项目,他们谈好了车型,材料,马力等等细节。之后,A带着团队做了6个月,做成了这架跑车,交给了土豪。可是土豪开了一天之后回来要求重做,原因是当讨论方案的时候,双方都忘记给跑车安尾灯了!

但是给跑车安装尾灯,需要重新设计车尾部,加上倒车灯,把车底拆开,安装线路,修改传动装置把倒车档和倒车灯联系起来

软件多变,而且很长周期后才让用户看到最终产品,反反复复

瀑布模型只能在产品的定义非常稳定;产品模块之间的接口、输入和输出能很好地用形式化的方法定义和验证

统一语言

软件行业相对其它传统行业有独特的难,同为构建,建筑工程在施工队进场开始构造之前,各种工程图样和材料都已经非常精确地准备到位,但在软件中没有足够时间收集所有需地,即使收集了,需求也不是从创建软件角度去描述

软件项目团队,从产品 到 开发,再到测试,应了那句不怕神一样的对手,就怕猪一样的队友,层层兜底,产品考虑不到的,开发帮兜底,开发不给力时,靠测试兜底,测试不走心的,那只能客户买单,名为兜底大队

项目开始,产品没有足够时间收集需求,但快速行动的重要性扎根程序员内心,没有足够信息来构建,可项目有交付期限,程序员只能进行创造性假设,填补产品经理留下的空白,只是为了保持项目不断推进。但产品经理或者客户经常改变主意,这意味着程序员对留白处的创建性假设经常夭折

项目实践需求与程序员的理解之间存在客观落差,但程序员负责最终完成项目,如果这种落差没有被注意或发现,就只能由程序员自己创造。这种创造是在没有指导或指示的情况下进行,当项目展示给客户时,客户才发现不是自己想要的,双方就容易争执

更多时候,程序员之所以在项目开始迟迟不亡友,是因为他们希望有人告诉该怎么做,但这种情况一般不会发生,这时就发挥自己的创造力,但这个创造有时会使项目偏离正确方向。如何发挥程序员创造力,同时又能保持项目的方向不被程序员带偏呢?

解决方法就是让程序员迟早参与创意过程,与业务专家、客户、产品经理一起参加头脑风暴会议,不断缩小双方思考或理解偏差。有逻辑性的正确设计会节省大量代码,因为编码实践来试错的代价是昂贵的。编码涉及大量技术细节,细到一个字段的字节数都需要考虑,一旦发现代码实现的功能完全不是客户要求的,就只能全部推倒重来。成千上万行代码被删除,好像它们没存在过,他们存在过的意义仅是让程序员明白:此路不通

程序员需要将业务知识的学习和思考结合起来,领域驱动核心就是多了一个领域层,这个领域是业务相关原语,随着业务价值变化而变化,通常每个企业的核心业务域是千差万别的,所以,在技术实现的时候,反复要问我们团队,我们多少比例的代码是针对业务本身,而业务本身对业务消费者有多大的价值?作为业务的技术实现方,一定要有手段通过量化方法,衡量你的业务价值,从而能精细化迭代升级

统一语言,并不是把从客户那里听到的内容翻译成程序自己的语言,减少误解,让客户更容易理解我们的草图,并且真正帮助纠正错误,帮助我们获取有关领域新知识,目标是创造可以被业务、技术和代码自身无歧义使用的共同术语

在PRD文档,设计文档,代码以及团队日常交流中,都使用同一套领域术语,极大地提升沟通和工作效率

简单理解起来的话,也就是把业务人员和开发人员的语言统一起来,用代码来感受一下大概就是:

1
2
userService.love(Jack, Rose) => Jack.love(Rose)
companyService.hire(company,employee) => Company.hire(employee)

通过统一语言,领域驱动设计的核心是建立统一的领域模型。领域模型在软件架构中处于核心地位,软件开发过程中,必须以建立领域模型为中心,以保障领域模型的忠实体现,解决OOAD中OOA分析模型与OOD设计模型割裂的弊端

朱兴生 wechat
最新文章尽在微信公众号『码农戏码』