前情回顾
在上一篇文章,我通过12306购买车票的例子详细阐述了业务服务的定义,以及该如何识别业务服务。有好几位读者热情参与了这一练习,完成的质量也都不错。有一位读者哀嚎说被我“批判”了,但随即也表示:通过对练习答案的点评,更有收获。
本次演练选出了一位获奖读者,由出版社赠送了《解构领域驱动设计》一书。该读者表示他已经购买了《解构领域驱动设计》,故而特别指出希望赠送重印后的《解构领域驱动设计》。
非常感谢读者们的支持与厚爱。我必须指出,初印和重印的差别微乎其微,除了少量勘误,并无其他差异。若想要了解有哪些勘误,可以戳这里。说起来,初印版应该更有收藏价值吧。
为何引入业务服务
说回正题。对于需求分析,业界已经提出了不少有价值的方法,其中最广为人知的是用例和用户故事。前者是UDD(Use case Driven Design)的重要输入,也是UML的主要元素,后者由极限编程的缔造者Kent Beck提出,普遍用于敏捷软件开发的需求分析。
既然如此,我为何还要引入业务服务这一概念?两个因素:
1
用例是有层次的。
如果站在整个企业的角度去思考用例的定义,就应以待开发的目标系统为边界,探讨参与者与目标系统之间的行为,从而形成业务用例;如果深入到目标系统内部,思考由系统提供什么样的行为以满足用户的需求,则为系统用例;如果再深入到用例层次,还可以体现高层用例和低层用例之间的包含关系与扩展关系,类似这样的包含用例和扩展用例作为子用例,可能只是主用例的一个执行步骤。
不同粒度的用例体现了不同的业务价值,当我们在谈论用例时,实际上需要明确用例的主体边界,恰恰对于这一点,并没有引起大多数需求分析人员的重视,交流中对用例的概念与粒度没有达成一致,从而产生各种标准不一的用例输出。
这并非用例的问题,但不可否认,它制造了正确理解用例的障碍。Cockburn就说:“编写者和读者经常把二者弄混,可能把系统行为放入业务用例中,也可能把业务操作归于系统用例。”
即便以系统用例而言,也因为目标不同而需建立不同的层次。Cockburn为其定义了三个层次:概要目标、用户目标与子功能。他分别用云朵/风筝、海平面、鱼/蛤给出各自层次的形象代表,下图是他给出的例子,来自他的著作《编写有效用例》。

这一分层的标准根据目标大小进行界定,我们可以将目标理解为用例对于Actor的价值。
即使各个层次的比喻非常生动而贴切,我们仍然无法就目标的大小或价值的高低做出一个相对客观的判断。
有兴趣的同学可以阅读Cockburn的这本经典著作,书中给出了大量的案例来区分不同目标的差异;然而就我的阅读体会来看,一方面为作者给出铁一般事实的案例而深深叹服,另一方面,在阅读完毕之后,回到自己要编写的用例时,仍有无从下手之叹。
究其原因,所谓分层的标准是一种跟着感觉走的经验之谈,而非一条条可以验证和检查的客观标准。例如Cockburn给出了如何测试海平面目标的原则:
这些原则显然不足以作为客观标准,教会我们如何确定用例的目标层次。即便Cockburn自己也说:“找出正确的目标层次是关于用例的一个最棘手的问题。”
2
用户故事的划分有一个经典的INVEST原则。该原则由六个单词构成,分别为:
漂亮的原则!我发现那些漂亮的原则总是由多个单词构成,而每个单词的首字母又能组成一个漂亮的单词(玩的是什么英文把戏?)——例如UNIX哲学中著名的KISS原则,OO设计的SOLID原则,单元测试的FIRST原则,当然,还要加上这里的INVEST原则。
原则虽然漂亮,却不过是一句正确的漂亮话罢了。仔细理解这六个单词,你会发现它们都出于主观的判断。一旦缺乏客观的判断标准,就会陷入“凭经验”的泥坑中,团队成员也难以就需求的层次和粒度达成一致。
3
大多数软件企业对需求规格说明书都有较为标准的格式定义,然而格式却无法决定内容的质量。更何况,我们费心编写的需求规格说明书究竟是为谁撰写的呢?它对开发团队究竟产生了多少价值?
相较而言,用户故事未必形成非常严谨的需求文档,但敏捷提倡的交流与协作,可以让用户故事呈现的业务需求更好地传递给每一位开发人员。
无论用例,还是用户故事,它们都强调角色与目标系统之间的协作,却没有做进一步的规范和约束,使得许多需求分析人员在编写用例和用户故事时,往往会杂糅两个常见的干扰项:
以下文本来自《编写有效用例》中的案例,它是一个海平面用例,用例名为“通过网络购买股票”:
1.购买者选择通过万维网购买股票。
2.PAF从用户那里得到所用站点的名字(比如E*Trade,Schwab等)。
3.PAF与该站点建立网络连接,并保持控制权。
4.购买者在该站点上浏览并购买股票。
5.PAF截取站点的响应信息,并更新购买者的记录。
6.PAF向用户显示更新后的记录情况。
它清晰地呈现了用户通过网络购买股票的过程,但是开发人员却可能弄不清楚究竟有哪些操作过程才是目标系统需要考虑的,也弄不清楚这些操作过程有哪些是需要他实现的。在理清楚了这些过程与待实现任务的关系之后,他还要进一步思考,该定义多少服务API提供给前端开发人员,又该怎么提炼其中的领域概念,完成领域建模。
业务服务不会这样。因为我之所以定义该概念,本身是结合领域驱动设计提出的。
业务服务规约
我在前面的文章提过:
所谓“角色主动向目标系统发起服务请求”,实际是指角色向目标系统的后端发起请求,如此就可以将用户的UI操作隔离到业务服务之外。
在领域驱动设计的背景下,业务服务是面向限界上下文的,为其编写的流程实际上是从限界上下文收到角色发送的服务请求后,需要依次执行的业务流程。
为了更好地阐述业务服务,我参考用例与用户故事的格式,给出了如下图所示的业务服务规约:

业务服务的名称采用动词短语形式,代表了一种领域行为,这一点和用例的要求一致。
对于业务服务的描述,我直接借用了描述用户故事的格式。一方面,这一格式要素直接涵盖了业务服务的角色与领域行为,另一方面还能促使编写人员思考它带来的服务价值。
我之引入触发事件,与业务服务的定义息息相关。该触发事件一定是由角色主动触发的,可以是用户点击UI的按钮,或者是策略满足了业务规则,又或伴生系统发起了对目标系统的调用。一旦触发了该事件,就会向限界上下文发起服务请求。
如此一来,对于业务服务的流程而言,一定是在收到服务请求之后开始执行的一系列连续的业务过程。因此,在业务服务的基本流程与替代流程中,根本不可能出现UI的操作,更不可能出现线下的流程。按照我的经验,业务服务执行流程的第一步,往往是对服务请求的验证。至于服务请求包含哪些内容,则可以认为是对应服务契约的输入参数。
验收标准的概念来自用户故事,通过它统一描述业务规则,同时,它也是需求分析人员、开发人员和测试人员三个角色就该业务服务达成一致共识的重要内容。
业务服务规约的例子
按照如上所述的格式,我们可以为“发布作品”功能编写一个业务服务规约。
服务编号:L0006
服务名:发布作品
服务描述:
作为作者
我想要发布我的作品
以便更多读者阅读我的作品
触发事件:
作者点击“发布文章”按钮
基本流程:
1.检查作品是否符合发布标准
2.对作品内容进行违规检查
3.发布作品
4.发送消息通知作品的订阅者
替代流程:
1a.如果作品不符合发布标准,提示“作品不符合发布标准”
2a.如果作品内容未通过违规检查,提示“作品内容包含敏感内容,禁止发布”
3a.如果作品发布失败,提示失败原因
验收标准:
1.作品标题字数不得超过50个字符(1个汉字为2个字符)
2.作品标题只能使用汉字、英文字符和数字
3.发布的作品必须包含标题、作品类型和作品内容,作品内容的字数不能少于300字
4.作品发布成功后,状态为“已发布”
5.作品的订阅者收到作品发布的通知
6.作品的订阅者可以阅读已发布的作品
理论上,应该由需求分析人员编写业务服务规约,同时,和测试人员共同完成业务服务规约的验收标准。业务服务规约呈现了领域知识,因此不要忘记了,每个词语的使用都要遵循“统一语言”的原则,即以正确的方式就每个领域概念、领域行为和领域规则在团队内达成一致。
业务服务的价值
在我的领域驱动设计统一过程方法中,产生设计驱动力的就是业务服务。业务服务体现了目标系统的领域知识,故而可以认为是以业务服务为核心的领域驱动设计,这实际上也弥补了Eric Evans在他的《领域驱动设计》一书中不曾提及需求分析的缺陷。
领域驱动设计讲究以“领域”为核心驱动力,居然没有阐述清楚到底该怎么获得以及如何呈现领域知识,逻辑上是讲不通的。
我定义的业务服务不止如此,它不仅是打通问题空间与解空间的桥梁,也是识别限界上下文时的主要输入;它不仅映射到解空间的服务契约上,还能帮助我们甄别限界上下文之间的关系;它包含的领域知识也成为了领域建模过程的重要参考,贯穿了整个领域建模过程。事实上,整个领域驱动设计统一过程的诸多实践与方法,都是围绕着业务服务来开展的。其价值如下图所示:

业务服务的价值是从四个视角来阐述的。
从需求分析视角看,它的定义明确了识别业务服务的客观标准,理清了混淆不清的层次与粒度,并通过业务服务规约清晰地呈现了它蕴含的领域知识。这属于问题空间的范畴。
到了解空间,在领域驱动设计统一过程的架构映射阶段,可以利用业务服务自下而上地按照业务相关性识别限界上下文。在我的书中,将问题空间识别业务服务和解空间识别限界上下文,统称为“V型映射过程”,它涵盖了问题解决过程的两个方向:自上而下与自下而上,恰好完美地组成了一个V型。
业务服务强调执行的连续性,又突出了目标系统的边界,并由限界上下文来响应角色发起的服务请求,将其映射到解空间,站在服务设计视角,就是一个服务API,我将其称之为“服务契约”。它就是在架构角度为限界上下文定义的对外公开的接口,也是业务能力的设计体现。
进入领域建模阶段后,业务服务规约为领域分析建模寻找领域概念提供了重要参考,进行领域设计建模时,业务服务规约中的基本流程甚至直接成为任务分解的输入,如果在领域实现阶段采用了测试驱动开发,则业务服务规约中的验收标准还可以帮助我们识别和定义测试用例。
显然,业务服务在我提出的领域驱动设计统一过程中,简直无处不在!如想完整地了解业务服务的所有价值,那就请耐心地将我正在撰写的这组系列文章完整读完吧。