DDD重构初步

所做的系统是一个连接到外部信息发送和搜索引擎调用服务的Web前端系统。系统与外部的接口使用的是一层Service外观进行包装,原先的设计目的是使用服务层剥离对外部系统的强依赖──解耦,同时还希望使用Service将系统的商业逻辑集中存放──提高复用的可能。但是实际上我们发现Service这样的抽取方法并没有提高代码的复用度,反而造成数据结构和其算法的大量重复。经过分析,发现由于使用了大量的Hash结构存放接口间的返回结果,造成数据与其Service内部的行为分离,传递后商业逻辑就出现了重复(这样的结果是引入大量Quick but dirty的解决方案所欠下的技术债造成的)。所以我和我的Pair绝对对其进行DDD(领域模型驱动设计)的重构。

领域模型驱动设计和面向对象设计在代码的抽象上很相似,它们都推荐让数据结构(状态)与商业逻辑(行为)统一管理。这样数据的状态与行为就不会分离,这样可以很大的减少由于商业逻辑分散造成的代码重复。这可以让我们实现我们非常重视的DRY(Don’t repeat yourself)。
一开始我们尝试的是找到重复的商业逻辑和其对应的数据结构,然后尝试让它们映射到我们的领域模型。这样的效果还不错,但是经过了一天的工作,我们发现我们对领域模型的理解有问题,我们居然抽取出了重复的领域模型类。这个时候暴露的问题是我们对系统的领域模型没有统一和深入的认识。

所以下一步我们计划进行领域模型语言的讨论会,目的就是使用自己的“领域语言”描述系统的所有行为,从这样的领域故事中找到我们的领域模型(领域概念模型,不是具体编程的时候的类)。

领域模型讨论会最好由最熟悉领域模型的人起草,可以由这个人把它写在白板上。领域的故事最好能够涵盖系统的主要行为,描述要使用简练的语言。一般来说IT系统的内部行为可能很复杂,但是到了领域高度还是可以用比较简单的语言描述的,如果遇到一块白板不够的话最好首先考虑提高观察的高度,让领域的描述简练一些,其次再考虑扩展到第二块白板。在这个过程中我们要注意消除歧义,比如同样一个“术语”在两个功能区域中出现,那我们就要妥善的给他们各自取一个容易区分的“领域术语”作为名字。还有就是我们应该在写领域故事的时候考虑领域模型(或者理解为系统模块的抽象)之间的交互关系,最好在写领域故事的时候对行为的归属(它会指导行为到底会被建模在哪个领域模型中,可以体现为类的调用关系,哪个类持有交互逻辑)达成共识,这个时候达成的共识比在做具体的OO设计时候分辨商业逻辑归属要更体现商业价值。完成领域故事后我们要找到其中的所有领域模型(也就是前面说的领域对象和领域中的术语)。然后团队最好一起通读这个用户故事,一起讨论是否通顺(行为是否完备,抽象是否合理),是否有遗漏(遗失的领域模型或行为)。对于有外部系统的情况下,即使外部系统是面向消息的(或者说是没有使用领域模型驱动的SOA接口),那么团队最好对消息的内容有一个讨论,对其中设计到本系统和外不系统的领域模型进行认领,然后使用一个适配器来保证数据到达系统后就使用领域模型表示,外不系统的数据(一般没有行为)最好也使用一个领域模型进行约定。

完成了上面这一步后,DDD最重要的一部分就完成了,它可以实现系统领域模型自顶向下的“名正言顺”,减少在自底向上的重构过程中产生的大量重复领域模型。下面的重构过程就是给领域模型写测试,使用TDD(或者也可以用BDD的方式提早对行为做验收测试,用它们来驱动对领域模型的实现,其实对领域模型的TDD和BDD是殊路同归)的方式逐一实现领域模型。而后争取给系统写一些验收测试或者高级别的集成测试,再逐一替换这些领域模型。这样就达到了DDD重构的效果。

Javascript库的“实现模式编程(Programming to patterns)”

今天看到了一篇很好的讲Javascript Library的文章:http://www.clientcide.com/best-practices/jquery-and-the-ajax-experience-programming-to-the-pattern-and-what-really-makes-one-framework-different-from-another/
作者是一位MooTools开发者,他写了对实现模式编程(programming to the patterns)进行了阐述。告诉我们像Prototype、MooTools这样模拟类继承并鼓励使用OO的方式组织你的代码对于你的好处。而对比的对象就是红火的JQuery。他的观点我举双手双脚同意。

JS的通用库最基本的目的是给你解决浏览器兼容性差异,让你在与浏览器兼容性的战争中给你一个巨大的后援保证。但是只提供浏览器兼容性的JS库是不会流行的,你还需要在编程模式和代码效率(Code efficiency)上提供帮助。对于我个人来讲,对语言的流畅性改造最好的就是Prototype这个库,因为它营造了一个“最小惊讶”的Ruby语言环境,让你几乎感觉到是在编写流畅的Ruby,尤其在Rails项目里面使用更让你感觉到Ruby和Javascript结合的流畅。(此处我想到了上周Cat Chen同学和我提起的“Language Oriented Programming”的概念,也许这就是它的表现吧,扩展语言减小编程中的“惊讶”)相比之下JQuery则在代码效率上做了很大功夫,这个代码效率不是指运行速度,而是单位代码完成的功能。JQuery对Dom的DSL化封装,还有对method chain的大量使用,几乎让你感觉在声明行为,所以它让非常多的对啰嗦的Dom编程厌烦的前端程序员迅速“上瘾”。但是,我们知道DSL化的JQuery还不够,因为它很好的解决了可读,但是并不一定容易维护(尤其是过度使用method chaning)。另外一个原因就是习惯使用“声明化”以后会让一些程序员忘记“抽象”和“封装”,很多JQuery都没有使用面向对象OO的方式去组织自己的代码,而是拥抱了面向过程编程,而很多人却还自我安慰说自己使用的是“函数式编程”。如果你没有做好函数抽象,那么绝大多数情况下你的JQuery code都不符合函数式编程的本意,此时你也就离抽象出可复用的“模式”越来越远了。

其实JQuery的很多UI plugins是很好的例子,尤其是支持Progressive Enhancement的JQuery UI插件,它们都很好的规定了自己的micro formats,然后根据dom的模式来组织自己的行为模式(Behavior and Patterns)。它们才是很好的对实现模式编程(programming to the patterns)的例子。缺少了对模式的抽象也就失去了在大的项目中实现代码效率(Code efficiency)的优势(因为代码复用度会下降),这对于JQuery这样一个以“Write less, Do more”为口号的库来说就失去了它最大的卖点。

所以,实际上关键的问题就是,用哪个JS库都没错,但是如何使用和形成模式才是关键。每个库组织自己的“模式化代码”的风格不一样。但是像MooTools和Prototype(还有YUI)这类基于类去组织代码的库的源代码本身就已经告诉了你如何组织你的代码,而且它们的OO实现对于任何一个熟悉OO的程序员来说都“没有惊讶”,那么你就很容易吸取他们所推荐的代码组织风格了。

最开始所引用的文章归纳了几个不面向“模式”组织代码的坏处:

  • 缺少抽象,缺少复用
  • 会形成零散的代码,修改起来成本会比较高,维护成本也就增高了
  • 不OO,不容易组织扩展

我补充一点:

  • 过程化的代码不容易测试,不使用模式的代码也会造成测试的零散,造成浪费

更新:有一点不准确,就是关于复用,其实Javascript的各种Widget库(如ExtJS、Dojo和YUI的widget库)都做到更好的复用。缺点是目前的widget库中的高级控件都严重的绑架了Dom结构,造成自己修改Dom结构比较麻烦。而没有使用Micro Formats这样的基于标准的弱耦合,这是一个很大的问题。所以由于这样的实现绑架的问题,这些Widget库不包括在上面所述的框架对比中。