DDD实践切入点(一)
前两篇:大型系统的支撑,应用系统开发思想的变迁
之前大致说了使用DDD的前期准备,现在可以真正开始实践了,以我刚刚结束的一个简单的经典DDD方式的项目为例子,当然由于比较简单,所以很多时候会脱离它来介绍一些额外情况,以及这些情况在《DDD》书上提到的解决办法,另外,说明一下,例子的作用只是例子,只是用来说明可以怎么做的,但真实情况时未必应该这么做。这一篇主要是大比例结构的。
首先,简单提一下项目背景,具体情况就不细说了。今年初公司重组,原本的一个公司拆分成了三个公司,信息化部门还是只有我们一个,经过小半年的时间,业务终于差不多确定下来了(稳定不稳定不好说),我也终于接触到了真实的业务(以往由于某些原因全是凭空猜的),作为例子的这个项目是做流程审批的,以此为基础分析后,发现非常适合使用DDD的方式进行设计开发。
前一篇提了,开始一个项目最重要的是要做的是确定需求中的核心问题,项目对公司来说目的是提高办公效率,而对我的领导来说更关心的是业绩,也就是开发效率,毕竟是公司内部系统,速度在一定程度上优先于其他。我做架构设计,我需求的核心也就是如何让程序员写更少的代码,更快开发出功能,这一点在架构设计开发过程中会占主导地位。
当然,前提是得满足完成业务的基本要求。在开始架构的设计前首先要确定系统的范围,系统都有什么,也就是一般说的系统边界。
可以使用大比例结构来确定系统的边界,当系统比较庞大时,可能会分为很多个模块,太多的模块依然会给人的理解造成压力,而Bounded Context虽然可以包含多个模块,但很容易让系统难以成为完整的整体,这时使用大比例结构可以让人清楚的看出系统的整体,借助一种更大范围的划分,可以清楚的理解元素在系统中位置。这时,当要寻找一个对象的位置,或新增一项功能的时候就可以轻松的定位到要找的位置,负责不同模块的人也可以清楚的知道自己所要做的部分对于总体和对于其他部分的作用,分工不同的程序员所做的设计决策可以大体上保持一致。
我的例子不大,使用大比例结构意义不大,基本可以分成两块,一个是审批申请单,一个是审批流程。审批流驱动原本的暂时还能满足需求,所以只需要设计申请单的处理就可以了,之后的事情虽然很简单,但是为了让它起到例子的作用,先来对它使用下隐喻,比如说,审批流是一个生产产品的流水线,申请单作为原料一步一步被附加各种加工也就是意见,最后成品的产品即审批结果。
对于业务比较复杂,模型中对象很多,无法一下清晰完整了解模型意义的,可以使用一些对模型分层的方法。这些分层方法的目的是使开发变得更容易。
一、Responsibility Layer模式:如果每个对象的职责都是手工分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持大模型的一致,有必要再职责分配上实施一定的结构化控制。观察模型中的概念依赖,领域中不同部分的变化频率和原因。如果领域中有自然的层次结构,就可以作为主要的职责。这些职责描述了系统的高级目的和设计。领域中的对象的职责都应清晰的位于一个职责层中,层都应该反映出现实状况,而不是设想的状况。
当需要按职责分层时,可先分为作业层和潜能层分别包含“作业”职责和“能力”职责,如果有需要根据业务制定决策时增加决策层,如果可以将指定决策的规则抽取出来时策略层,以及介于作业和策略之间的承诺层,Evolving Order具体使用需要看自己情况随机应变,具体哪种方法好,要不要这么分层都是根据自己的实际情况决定的,感觉到别扭就要考虑换一种方法,切忌生搬硬套。
潜能层:资源(包括人力资源)一集这些资源的组织方式是潜能层的核心。这一层关心的是能做什么。潜能也包括临时资产,但主要依赖临时资产的可将临时资产独立一层,比如Capability层。
作业层:正在做什么,利用潜能做了什么。通常作业层对象可以引用潜能层对象,甚至可由潜能层对象组成,但潜能层对象应该引用作业层对象。
大部分此类领域系统中,这两层就够了。它们可以跟中当前状态和执行的作业计划,已经问题报告等。但跟踪往往是不够的。当项目腰围用户提供指导或自动制定一些决策时,就需要另外一组职责。
决策支持层:应该采取什么行动或制定什么策略。这个层是用来做出分析和制定决策的。它可以对较低层的信息进行分析。它对较低的层如作业层或潜能层有概念上的依赖。
策略层:软件实施了详细的业务规则或法律需求,这些可以形成一个层次。可以使用规则引擎,也可以作为参数传递给其他层的方法。
在一些特别情况下,潜能层可能会被合并到作业层,比如保险公司在考虑签保单承担理赔责任时,要根据当前业务的多样性判断是否有能力承担风险,这类情况通常会出现一种层次是对客户做出的承诺。这个层次具有策略层的性质,因为它表述了一些指导未来运营的目标;同时也有作业层的性质因为承诺是作为后续业务活动的一部分而出现和变化的。潜能层和承诺层并不互斥,可以并存。但是一定要注意,层次不要太多,否则为了解决复杂性使用的分层层次就会造成新的复杂性,大比例结构要保证严格的精简。
另外,在分层时,可以借助并保留一些有用的特征:
1.场景描述:层应该能够表达出领域的基本实现或显示出业务的优先级。
2.概念依赖性:较高层概念的意义应该依赖较低层,低层概念的意义应该独立于较高的层。
3.概念轮廓:应该能够容许不同层的对象之间必须的不同频率的变化。
举个分层的例子,首先从两层开始,作业层是计划的活动,日常活动的核心,比如我的例子中作业的对象就是申请单据,包含相应的单据类型的流转规则、条件和计划要流转部门的职位。潜能层反应执行作业时所能利用的资源,比如。。。(话说这个比如我想了很长时间,因为是拿项目硬充例子没办法)审批节点上有权限的部门职位的人,也就是申请单流转到需要某个职位审批时,这个职位上的人或人们之一是可以审批的。如图:
上图中路径选择并不是当前的作业,而是用来决定流转的下一个节点,流程线路分叉时选择流转方向或者改变流转线路的。这时第三层“决策支持”层出现了,这一层为用户提供用于指定计划和决策的工具。它具有自动制定一些决策的潜能(如某原料采购成本变化,自动选择该申请单由事业部经理决策还是主管副总裁决策)。 这里需要说明的是“流程线路”,流程线路中包含所有流经的部门职位,是对当前申请单的一个总体的作业范围,当前申请单并不一定会路过起内的多有路线,而决定其路过路线的就是”路径选择“。以上,路径选择应该放入决策支持层,如图:
至于策略层什么的,这个项目是在简单了点,就不举例子了。另外,有时会有些特别情况,比如说作业层的处理结果出现了错误,比如说流转节点的判断条件出现了例外情况不符合规则,这种情况需要决策层提供如何处理,是退回还是通知,但较低层是不能依赖较高层的,这种时候可以采用事件机制,作业层状态变化时产生事件由策略层监听。如果事件违反规则,则执行特定的规则处理,或生成事件反馈给决策层以便决策层决策。
二、Knowledge Level模式:一组描述另一组对象应用哪些行为的对象。两组对象的关系在表象上有些类似于贫血模型及其对应的服务类,只不过这里的贫血对象和服务类都是对象或聚合。一组对象是我们需要的模型,而另一组对象是一个描述模型的模型。模式的两个级别分别是知识级别和操作级别。创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分成为两个级别,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
这一模式主要解决的问题是,如果在一个应用程序中,对象的角色和他们之间的关系在不同的情况下有很大变化,为了兼顾各种不同的情况,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。再勉强举个例子吧,流程审批时,当审批职位为合同管理员时,有一项特殊职能,是分配合同号,使用此模式来表达设计如图:
这个图参考个意思就好了,不要关心它是什么图,也不要在意规不规整什么的。。。
三、Pluggable Component Framework 模式:基于相同的一些抽象但分别独立设计的程序互操作的情况,可以通过将这些程序设计为组件,通常所有组件都插入到一个中央hub上,这个hub支持组件所需的协议(可能有多个多种),并且知道如何与它们所提供的接口对话。从接口和交互中提炼出一个Abstract Core,并创建一个框架,这个框架要允许这些接口和各种不同的实现被自由替换。同样,无论是什么应用程序,只要它严格地通过Abstract Core的接口进行操作,那么就可以允许它使用这些组件。高层抽象被识别出来,并在整个系统范围内共享。
这种模式有几个缺点。一个是它需要高精度的接口设计和一个非常深入的模型,以便把一些必要的行为捕获到Abstract Core中。另一个很大的缺点是它只为应用程序提供了有限的选择。如果一个应用程序需要使用一种非常不同的方法,那么可插入式组件框架将起到妨碍作用。
这种模式的使用并不常见,我这个项目有一个类似的用法,上面说的的申请单,申请单聚合大概如下图:
这个聚合只是个抽象的聚合,其中业务信息有可能是项目,有可能是合同,也有可能是发票等等,申请单信息也会随着类型不同而有不同的内容,明细就更不用说,这个明细就是一个抽象核心,不同种类的业务信息和申请单就可以通过继承这个抽象核心并实现其中接口来实现插入,与系统中其他的模块、上下文进行对话,比如说被流程审批等等。
大比例结构大致就这样了,如果不顺利的话,后面我会把这个项目细致的事情都写一下的,或许。准备是作为一个DDD入门的系列来写,不过我比较随性,而且喜欢走神,看缘分吧。。。