前文勘误
有人阅读我的文章,并指出文章错误,作为作者的我,应该表示真诚的感谢。文章写来没人读,不就是抛媚眼给瞎子看吗?有人指出错误,不仅能帮我勘误,还能增加知识,岂非一举两得。
所以这里必须感谢潘加宇老师就前文指出的错误。
前文内容中的错误出在这里,文中写道:
如果站在整个企业的角度去思考用例的定义,就应以待开发的目标系统为边界,探讨参与者与目标系统之间的行为,从而形成业务用例;如果深入到目标系统内部,思考由系统提供什么样的行为以满足用户的需求,则为系统用例。
确实大错特错!我将业务用例和系统用例的主体边界都写错了。当时看了,真让我大吃一惊!我的第一反应是,莫非《解构领域驱动设计》的内容也写错了?赶紧翻书——谢天谢地,书里的内容还好是正确的:
用例(use case)是对一系列活动(包括活动变体)的描述;主体(subject)执行并产生可观察的有价值的结果,并将结果返回给参与者(actor)。如果将整个组织作为用例的主体,参与者就应该是组织外的角色,用例表现的就是该角色与组织之间的一次交互,此时的用例称为业务用例,代表了组织的本质价值;如果将目标系统作为用例的主体,参与者就变成了目标系统外的角色(人或者外部系统),此时的用例称为系统用例,表现的是角色与目标系统之间的一次交互,通过这种交互,参与者获得了目标系统提供的业务价值。
看来,写微信公众号也不能太随便啊!
我以前在ThoughtWorks时,经常会阅读MyThoughtWorks(公司的内部Wiki)上各位twer发表的文章,汲取养料。有幸能经常拜读到老马(Martin Fowler)的文章。
老马有一个习惯,在将自己写的博客文章发布在他个人网站(martinfowler.com)之前,总是会将其发布在MyThoughtWorks上,征求各位twer的意见。如此公示多日,确定文中没有问题了,才会最终公开发布到网站上。如老马这般世界级大师,行文却如此谨慎,令人佩服!
然则——我以小人之心度之,恐怕像他这样的大师,写作怕是要小心翼翼、如履薄冰吧?
如今我真正体会到了这一种心情。
可是,我不想变得这样累,写篇微信文章还要像写论文那样引经据典,小心查证?——何苦呢?我可没有自诩自己为读者之人生导师!错就错吧!错了,那就态度端正一点,错了就改。所以,还是让批评来得更猛烈一些吧!
即便没有错误,对于不同观点,自然需要争辩。我也欢迎!真理越辩越明,即使辨不出真理(软件设计有诸多内容没有真理可言),至少也能让思路更清晰,理解更深刻——只是,不要借此打广告就好。
前情回顾
回到我的系列文章。
在上一篇文章《业务服务的价值在哪里》中,我对比了用例、用户故事和业务服务的区别。或许我不应提出此对比,由此会引起一些不必要的争论,也有抑彼扬此、王婆卖瓜的嫌疑。
作为一名至今还在写代码的程序员,我所谓的业务服务之价值,确实是来自开发一线的感受。前文已经“表白”清楚,这里也就略过不提了。
前文还提到了业务服务规约。若要在细节层面传递领域知识,需要为业务服务编写业务服务规约才可。然而在全局分析阶段,一开始不必钻入细节,因为它之输出将作为架构映射阶段的重要输入。如果从分析阶段就沉入太多细节,就会陷入“分析瘫痪”,且无法帮助我们尽快获得合理的架构。
因此,我对业务服务的介绍就先告一个段落,让我们快速进入架构映射阶段。重要的,是我们要识别出限界上下文。
要识别限界上下文,需要了解限界上下文到底是什么?我的著作《解构领域驱动设计》用了大量篇幅来阐释限界上下文,因此,在这组系列文章中,我就仅列出浓缩的精华。
限界上下文的六个要素
首先是限界上下文的六个要素,我用一句话进行描述:
封装了领域知识的领域对象,在知识语境的界定下,扮演了不同的角色,执行了不同活动,共同对外公开内聚的业务能力。
这六个要素之间的关系体现为下图:
限界上下文主要封装了提供领域知识的领域模型对象。在划分领域驱动设计的边界时,要根据领域知识的边界进行划分。在其内部,这些领域对象与基础设施提供的功能共同向外以服务形式提供各个完整的业务能力。
限界上下文的两个本质
限界上下文是解空间的子空间,它体现了如下两个本质:
1
如何理解“领域模型的知识语境”?我们可以想象一名伪装者穿行在不同的空间中,扮演了不同的角色,他的身份由其所处的空间决定。这个空间,就是我们进行讲述的知识语境。
可以换一个角度理解“盲人摸象”这则寓言。如下图所示,它隐含说明了在限界上下文的限定边界内,定义的领域模型虽然是局部的,但对于当前限界上下文而言,它就是整体。
在目标系统范围内,一个完整的领域概念就是一头大象,例如Customer的方方面面作为一个整体,Product的方方面面作为一个整体,它们都是完整的一头大象。但是,在限界上下文的团队里,人们看到的大象的局部,在他们眼中,不是局部,而是大象的整体。——是否有柏拉图洞穴理论的哲学观?谁又能看到现实的真相呢?
采用模块的设计观念观察大象,大象是一个唯一的整体:
采用限界上下文的设计观念观察大象,还是这个整体,但是在逻辑上被分为了不同的部分,受到知识语境的限制,每个团队都将自己看到的局部看作是整头大象,于是,在各个上下文中都定义了各自的Elephant,到了系统层次,它们通过唯一ID确定大象的身份,又共同组成一个整体。
以Product为例,完整的Product属性有数十个,但在运输上下文,只需要了解Product与运输有关的属性,如shippingWidth、shippingHeight、inShippingBox等。此时,就应该在运输上下文定义一个Product类,它拥有这三个属性,在运输上下文中,它就是商品整体,也就是整头大象。
2
限界上下文是业务能力的纵向切分。
这一本质旗帜鲜明地说明了:
与限界上下文相比,我们通常使用的“模块”概念就不同了,它的边界没有这么清晰,既可以横向切分,也可以纵向切分。由此方式定义的业务模块只定义了具有领域知识的领域模型对象,却无法对外提供完整的业务能力,它需要基础设施模块的支持。如下图所示:
限界上下文的边界由领域维度决定。不考虑粒度的差异,完全可以将限界上下文当作一个相对完整的子系统:
正所谓“麻雀虽小,五脏俱全”,如图看到的限界上下文虽然粒度更小,但它是功能相对完备的。这就解释了为何在进行微服务设计时需要借鉴领域驱动设计的思想。
模块的划分是做生日蛋糕的做法,即从实现的角度,先做好一层蛋糕,再添加巧克力,之后为其铺上奶油,最后点缀各色水果。
限界上下文的划分是切生日蛋糕的做法,即从消费的角度,给每个人切一块蛋糕。理论上,不管切下的蛋糕有多小,都是相对完整的:蛋糕、巧克力、奶油和水果俱全。
如此切分的前提基于一个事实:大多数软件系统的需求变化,是根据领域维度进行变化的。要保证设计的架构具有演进性,不是要拒绝变化,而是要让变化产生的影响降到最低。故而,架构设计需要顺应变化的方向。
限界上下文的四个特征
一个设计良好的限界上下文必须满足自治性。限界上下文的自治特征如下图所示:
简单总结这四个特征:
菱形对称架构
与限界上下文自治特性对应的架构模式是我提出的“菱形对称架构”。
或许又有人说我“创新”何太急了。但我认为:在任何领域,我们都要敢于去创新。创新不一定要开天辟地,哪怕是一个小小的“新想法”,也不可否认其价值;创新也不一定要批判过去,哪怕是对现有成果的小小改进,也可视为技术的持续前进。至于什么是真创新,什么是伪创新,就要看以谁的标准而论了。
不可否认,菱形对称架构脱胎于六边形架构,思想则沿袭自整洁架构思想。与之不同的是,菱形对称架构是特别针对领域驱动设计的限界上下文提出的。
一句话形容菱形对称架构,可以总结为:内外分离,南北对称。整个架构模式如下图所示:
菱形对称架构的英文描述为Rhombic Symmetry Architecture(RSA),也可以简称为菱形架构(Diamond Architecture)。
1
所谓“内外分离”,就是在菱形对称架构中,整个限界上下文被分为内部的领域层和外部的网关层。
内外分离的风格可以更好地隔离业务复杂度与技术复杂度。团队根据菱形对称架构编写代码时,一个基本的检查手段就是询问:我写的代码与领域逻辑有关吗?如果是,就放在内部的领域层;如果非,就放在外部的网关层。进行代码评审时,也可通过这一判断标准进行检查。
限界上下文内部的领域模型需要满足“最小完备”的自治特征,外部的网关层提供了业务能力需要的基础设施,满足了“自我履行”的自治特征。
2
所谓“南北对称”,就是南向网关和北向网关的对称,前者体现“抽象”思想,故而分为端口与适配器;后者体现“封装”思想,分为本地服务与远程服务。
为了更好地隔离领域层变化对外界的影响,外部的网关层就好似鸡蛋壳一般保护着鸡蛋,避免直接将领域模型裸露在外。因此,在引入本地服务与远程服务的同时,还需要引入一层消息对象,它建立了服务契约与领域模型之间的间接层。
通过引入南向网关,可以满足限界上下文“稳定空间”的自治特征;通过引入北向网关,可以满足限界上下文“独立进化”的自治特征。
3
如前所述,菱形对称架构很好地满足了限界上下文的四个自治特征。同时,它还遵循了稳定依赖原则,该原则要求被依赖的元素(类、模块、服务)要稳定。这实际上是控制依赖复杂度的有效方法。
遵循该原则,菱形对称架构的南北网关分别体现为:
4
我之所以说菱形对称架构是特别针对领域驱动设计的限界上下文提出,还在于它直接反应了Eric Evans提出的上下文映射。同时,它也可以和分层架构映射起来,如下图所示:
南向网关扩大了防腐层(ACL)的外延,从限界上下文的代码模型边界来看,不止是上游限界上下文,诸如数据库、文件、外部平台、消息队列都是它要防止腐化的内容。为此,我将访问数据库的抽象Repository放到了南向网关的端口层,与访问上游限界上下文的Client位于同等地位。对应领域驱动设计的分层架构,南向网关就是基础设施层。
北向网关就是开放主机服务(OHS)。由于Eric Evans强调应用层需为一个薄薄的层,不应包含领域逻辑,故而也可认为应用层的应用服务就是开放主机服务。然而就此二者,在Eric Evans的书中语焉不详,为保证概念的清晰性与一致性,我干脆将应用服务认为是进程内调用的本地服务,然后通过区分通信协议与设计模式,分别定义了不同的远程服务。对应领域驱动设计的分层架构,北向网关就是应用层。
开放主机服务需要定义发布语言(PL),以保证其稳定性。在菱形对称架构中,就是定义的消息。
如果为限界上下文引入了菱形对称架构,除了共享内核模式,如防腐层、开放主机服务与发布语言等上下文映射模式就已经包含了。至于为何不用分层架构,是因为我觉得像菱形对称架构这样内外分离的表现形式,可以更清晰地体现业务与技术的隔离。
关于菱形对称架构的定义与运用,有很多内容要谈,各位可以去阅读我的书籍《解构领域驱动设计》。当然,它也可以很简单,只要你理解了抽象与封装的思想,那就只需要记住这八个字即可:
内外分离、南北对称。