Monthly Archives: August 2018

DDD与面向对象设计

DDD最近几年比较热,因为它在某种程度上确实解决了一些设计问题,它强调在软件设计的初期就引入领域专家,始终围绕着软件要解决的核心领域问题来展开,很好的避免了一些架构过度设计,过早的引入非领域问题设计,比如事务,并发。它围绕着几个核心概念——Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event——来设计并实现功能,整个团队用一致的工程语言来描述和组织相关的代码,比较有效率,代码质量也有一定的保证。但是它也有其局限性,我用《面向对象分析与设计》的解密文本的应用来举例子,题目描述很简单,给定一个密文,设计一个解密程序。如果生硬地沿着DDD的思路,我们发现很难去识别相关的Entity, Value Object, Service并展开相应的设计。对于这样的问题,面向对象设计给了一个非常漂亮的解答,它模拟了一个写了密文的黑板,有众多拥有某些知识的观察者在观察并根据自己的知识——比如单字母在英语中,一般是I或者A;比如三个字母最后一个是E的很可能是THE;比如结尾连续两个字母一样很可能是EE …… ——尝试着解密部分密文,然后通知其他的观察者,从中我们可以学习到面向对象设计如何针对一个很难入手的问题,通过分解一个一个细粒度的对象,每个对象只解决一部分小的问题,最终各个对象通过交互,来得到最终的答案,所使用的 Blackboard模型 不在这里展开讲了,有兴趣的读者可以自行深入了解,非常推荐大家认真学习这个问题解决过程,能够很深切的体会到面向对象设计的思维。从中我们可以看出面向对象设计一般不关心数据,它更关心的是行为。一个问题在现实中如何被解决,并通过模拟这个解决过程,定义其中所牵涉到的对象,行为,以及交互,从这方面说,它与DDD是不太相同的。无论DDD如何强调要设计充血的Entity模型,在落地过程中,识别Entity和Aggregate的阶段非常容易陷入设计出大量贫血Entity的情况,通过setter/getter,而不是清晰的包含领域意义的行为来操作数据;陷入过早的考虑数据之间的关联而不是对象彼此之间的交互的陷阱。

DDD本身并不是传统数据驱动设计的变形,尽管数据驱动过去是,现在仍然是一种非常有效的设计方法。

Get the data structures correct, and the code will write itself.

早期的C程序员解决问题,都是定义好相关的struct,详细到xxx字段是用int还是char,然后围绕struct来开发相关的方法并组织这些方法的调用过程。传统的企业级应用开发也是先做好数据库的设计,整个应用层是围绕着数据库来开发的。说到这里,想扯一点Go语言的语法设计,只是个人感觉,因为Go语言的设计者们都是资深的unix c程序员出身,Go语言的面向对象的语法设计是有点别扭的,struct embedding,带接收者的方法定义,带来了继承,多态,覆写概念上的一些理解障碍。

既然谈到了数据驱动设计,就再多说几句Kubernetes和Spring Cloud,现在这两种是常见的微服务架构落地方案了,他们就有很大的不同,Kubernetes通过定义精妙的Pod,Deployment,Service,DaemonSet,Ingress, CustomResourceDefinition等数据结构,所有的核心组件通过监控etcd的数据变化来调整自身的状态,在不增加复杂性的基础上提供了很好的扩展性。反观Spring Cloud,基于面向对象的设计,提供了一个又一个的抽象,架构的复杂性和扩展性形成了正相关的关系,导致Spring Cloud整体的复杂度越来越高。非常佩服Google的Kubernetes的整体架构设计理念以及定义核心数据结构的功力。

DDD是一种自底向上的设计,非常适合企业级产品的团队开发,因为企业级产品绝大多数都是围绕着明确的领域数据来实现功能,而且DDD有很多好的实践与模式帮助解决很多开发过程中常见的问题。

面向对象设计是一种自顶向下的设计,更普适一点,更难掌握一点,很难归纳出有明确的步骤指导解决某一类问题,正如《面向对象分析与设计》中说的一样,在很多情况下,完成详细设计后,识别Entity也是其中的一步,不过是在相当靠后的设计阶段了。

DDD和面向对象设计都是核心领域建模的方法,要完成真正的产品,还有很多方面要考虑。在软件开发领域,还跟多年前的论断一样,没有银弹!

最后,安利两本书,《面向对象分析与设计》《实现领域驱动设计》