领域驱动设计在大规模项目中的应用心得 --发表于《软件世界》08年第1期

 

图1 孙向晖在软件技术大会的沙龙上讲解领域驱动设

几乎每种语言、开发框架和工具面世时,都号称要把开发人员解放出来,让他/她们更加关注于业务逻辑的处理。但目前的工作状况是,每当一个新的“解放工具”出现,就会有众多的技术狂热者蜂拥而上,而忽略了他们最应该关注的业务逻辑部分。还好,从2004年开始,“领域驱动设计”的声音开始浮现,开始逐渐扭转这种局面。

 

领域驱动设计词解

 

尽管提出人Eric Evens 已给出了领域驱动设计的官方的解释,但始终让人感觉存在一些困惑。笔者将从一个工程实践者的角度来重新对这些词做出简要的解释。

 

领域

Eric给出的解释是:“用户应用软件的主题区域”。那么我们应该如何理解这句话呢?例如,如果有人想把个人的社会关系写程序展现出来,类似于“我是孙向晖,我是豆豆的父亲,我是我父亲的儿子。”有多少人会感觉到这段可能出自“好玩”的程序的价值?

但是,国家正在建立个人和企业的全国征信系统,以建立信用档案,规避“我给豆爷做汽车贷款担保,豆爷给豆豆做房屋贷款担保,豆豆给我做商业贷款担保”这样的风险,这样以来,个人的社会关系就成为非常有价值的基础信息建设。

因此,软件的主题区域应该是在浩瀚的知识和客户要求(Customer Request)中被精心选择和摘取出来的部分,用来解决实际的业务问题。但是经常被项目研发团队混淆或者忽略的是:客户需要不一定就是有效需求。

很多团队将自己的工作重点放在了满足客户的变更需要上,结果客户满意度反而下降了。在这种情况下,这个团队更应该审视一下自己的变更控制流程,目前团队实现的是不是太多的客户需要而非有效需求呢?

一个软件涵盖的业务范围会非常的广,我们需要继续根据二八原则来进行识别,以便找出其中的核心领域作为软件开发的重点。

为了确定软件的领域,我们通常可以问如下两个问题:

1.软件实现后的核心业务价值是什么?

2.谁会为这个核心业务价值的实现来买单?

驱动

“驱动”这个词近些年来不绝于耳,耳熟能详的如:用例驱动开发 (UDD)、特性驱动开发(FDD)、测试驱动开发 (TDD),驱动的本质是什么?

“驱动”就是试图让我们的思路更顺畅,场景的切换更平滑。“领域驱动设计”就是希望在我们的设计中首先体现的是核心领域的选择和实现。我们做出来的设计跟所选择的领域之间更具有可追溯性、可度量性。

领域发生了变化,领域模型和设计就要发生相应的变化;设计变化了,一定能够追溯到领域的变更。引发设计变化的因素很多,一定要是关键的领域部件所带动的。

 

模型

虽然,我们可以从众多的线索中抽取出比较核心的部分(也就是领域),但是领域还是太大,所以我们需要对领域的不同的侧面进行描述,以期团队成员可以很好的跟领域专家进行沟通,不至于在若干的岔路前迷失。

 

领域专家

为了获得真正的领域,应该让领域专家进入团队。这个角色对于项目的成败至关重要。请紧记,一个项目团队之所以能够正确地从业务线索中筛选出核心领域,其前提是团队中有一个真正的“领域专家”。而现在我们通常见到的是让一些技术人员去“编写”需求。

那么将上面的词汇进行汇总,我们最后能够得到的是什么?

如果要做到“领域驱动设计”,那么我们需要和真正的领域专家一起工作,从众多的线索中,筛选出最能够体现核心业务价值的领域,为它建立一个可以设计和实现的领域模型,并在团队中始终使用这个模型进行交流沟通。

 

常规项目的领域建模6步法

 

如果不考虑太多的因素,只是想在项目团队中实践“领域驱动设计”,那么我们可以遵循一个简单的6步法。

1.提取概念。在这一步骤中,应该尽量多的进行领域走查,从文档资料或者交流过程中发现并标记出更多概念,尽管这些概念会有重复或者被混淆的可能,尽量先不要打断自己的或者领域专家的思路。

2.标注关系。概念的关系会有很多种,但最重要的就是两种,关联关系和泛化关系。因为两个概念之间可能有若干种关联关系,所以,应该在每一个关系关系上标记关联方向、关联名称和多重性。注意,关联应该尽量根据业务逻辑形成单方向的关联,关联关系的箭头方向也不要跟数据模型中的外键关系相混淆。尽管目前出于各种原因,很多的语言很难实现出这种强语义的单向关系,但作为对业务的深入理解,将其简化更容易消除我们思维中的领域复杂性。

3.分清类别。目前存在很多种对领域概念的分类方式,DDD中提出的分类方法是:实体、值对象、模块、服务、资源库、限界上下文等,Peter Coad在自己的《Java Modeling in color with UML》中提出了自己的分类方法,如Party/Place/Thing,时间/间隔等,还有我们最常使用的系统、子系统的分类方法。

我个人的实践观点是,不一定要选择哪一种固定的分类方法,应该根据业务的实际情况,合理的选择一种或者几种分类作为自己的分类方法。

4.深究疑问。可能有人对为什么要进行分类(第3步)心存疑惑。分类的最大的目的是为了形成高内聚低耦合,其次通过分类,还能够让我们将分类剩余的或者类别中不和谐的部分查找出来。对于任何疑问,都应该记录下来,并且应该找领域专家进行讨论,并澄清其中的概念。

在深究疑问的过程中,经过一定的领域知识的积累,可能会达到从量变到质变的突破。虽然突破很难出现,但只要它真的出现,就会给整个团队带来一种新的开发思路,领域的复杂度往往会随着“突破”的出现而发生变化。

其实在现实生活中,我们眼见后第一感觉并不一定就是真实的领域逻辑,比方说,我们会在日常的交流中大量的使用规约(Specification):“某文档创建人的所有的直线上级都可以查看该文档”这句话经过领域建模后,可能会形成以下的要记录的规约:“某种文档类型,其创建者在‘行政编制’中的所有的直线上级都具有‘查看该文档’的权限”;而“办理资产入库登记”会演变成“登记某种资产类型的一批号段的批量入库”。

5.映射到数据模型并实现。作为企业级应用,大部分的领域模型是要被存储下来的,包括那些隐式的概念。隐式的概念的处理有两种方式,一种是直接运算后记录运算结果;一种是记录下规约,在运行时解释。前一种方式的执行速度会很快,但是规约适用的范围发生变更时调整起来比较麻烦,后者执行时运算速度慢,但是应对变化能力更强些。因此,可以将这两种方式结合,同时记录规约和规约运算的结果。

我们现在在项目团队中的做法是,是将核心领域中的业务需求细化成可度量的用户故事,采用“测试驱动开发”的方式整理测试用例和测试夹具(fixture)。这种方法的好处是整个的需求把握是从领域模型出发的,不会将需求的覆盖和设计的推导寄托在持“技术思考”的开发人员身上。通过实现带有检查点的用户故事,提炼出来的接口往往是最实用的,会对“预先设计”产生的工件产生改天换地的效果。

6.就如同“降龙第十八掌就是要将前面的十七掌连起来再打一遍”一样,是一个迭代的过程,需要根据变更的情况对领域模型进行重组和调整,为了方便的进行重构的同时保证质量,应该在项目团队中实施自动化的项目构建,并确保持续集成。

 

在大规模项目应用

 

我们前面说的所谓的“常规项目”是指从头开始开发一个新的项目,基本上是遵循的从上到下的项目研发模式。但是,经过若干年的信息化,每个企业都已经或多或少的拥有了自己的信息系统了,推翻了重新开发是行不通的。

我个人认为,领域驱动设计之所以会如此获得很多人的青睐,跟它最初将自己的应用场景放到了企业架构层面不无道理。

企业中的组织机构中有多种组织编制关系,每种组织关系中会有不同的汇报和隶属关系。有的组织机构同时具备水平管理或者垂直管理,如此,这样的组织架构所执行的业务的含义也会有很大的不同。

企业架构中的应用架构视图,告诉你有多少遗留的系统,每个系统着力于去解决在企业管理中的某个层面中的业务问题。

企业架构中的开发架构视图,告诉你有多少个开发团队和维护团队在现有的或者将来的系统上工作着,那么这些团队之间都是什么样的关系呢?他们应该如何协助才是合理的?

在大规模项目中,为了要保证信息化建设的延续性,保护已有的投资,就要在系统建设开始之前,进行大规模的领域走查,在分类时要确定更大规模的信息架构用于跟企业架构对应。常用的方法有:

1.精炼:一个项目足够大时,不是所有的子系统都具有一致的优先级,我们应该对核心业务和支撑类业务进行区分,对核心领域和通用的子领域进行区分,对要优化或者要删除的领域部分进行区分。

2.职责分层: 因为组织机构是分层的,所有业务组件和领域模型也是分层。不同的层级关注的点不同,这点需要尤其注意。

3.上下文映射:当拥有不同的研发团队、遗留系统时,就会自然的形成若干的限界上下文,有的领域概念在不同的上下文中含义不同,例如:一个客户,在同一个系统的呼叫中心上下文中是访销客户,在CRM上下文中是零售终端,在物流上下文中是配送客户,在专卖上下文中是被监管的客户。要在一个模型中维护所有的信息是不太现实的,应该在各个上下文中建立起概念间的映射关系。

4.强化(highlighted):如果这个系统足够大,大到让所有的人一看那些细节就晕,那么就应该在其系统远景的基础上,整理出一幅能够轻松让团队每一个成员都能达成共识的强化领域模型。在超大规模项目中使用这样的模型,会让团队的领域整体把握能力大大增强。

 

真实案例应用

 

我想最后还是可以哪出我们最近的一个“全面预算管理”系统的例子来跟大家分享。这个系统是一个非常大规模的系统。要在集团公司和旗下的若干个子公司中部署实施,每个子公司要在集团统一的预算体系下有自己的特色(这是中国的企业最希望强调的一点),预算的过程要从上而下然后从下而上的反复过若干次,并且各责任中心的预算的版本要有保留。

最后,根据预算的实际执行结果要跟预算额进行比对分析,以便为考核以及下一个预算周期的预算打好基础。无论是预算体系的设计还是预算的编制或者变更,都涉及了诸多的业务部门参与,另外研发团队分成若干个部分,还有要和若干的遗留的业务系统以及财务系统、考核系统对接。

在接收这样的大项目时,我们的第一步是要做企业架构的采集和评估,了解集团企业的战略目标,明确系统建设的长期目标和短期任务。比如:如果企业“全面预算”的长期建设目标是提高企业的“预算精准程度”,那么它希望通过几年的建设达到该目标;那么企业“全面预算”在年内的短期目标是实现预算编制和修订工作的电子化吗?

针对确定下来目标,进行领域走查,确定热点的业务组件。我们会根据其组织架构将业务组件和领域模型进行职责分层:

1.决策层 全面预算体系的设计。

2.执行层 全面预算工作的编制和修订。

3.控制层 全面预算编制的评审和执行结果评估。

如果第一年的建设目的是实现编制和修订工作的电子化,那么决策层的工作一定要做,但不应该将它作为一期软件实现的重点。我们可以采用批量导入的方式来建立企业的全面预算体系。

为了更大程度的保护既有投资,我们需要考虑“全面预算管理”系统和其他系统之间的业务流和信息流向:哪些信息流是要求同步的,哪些可以是异步的。为了保证各研发团队不发生集成大爆炸,各限界上下文之间的集成部分要提前考虑,并建立定期的沟通交流机制。

领域驱动设计不光给我们带来了理解企业信息化本质的最实用的思考方式,还给我们带了切实可行的实现模型。在IBM SOA的解决方案中,“领域驱动设计”被其推荐为提炼和实现SOA服务的参考方法。我们在众多的大规模项目中成功的应用了这一敏捷方法学,愿与更多的朋友一道在项目中积累自己的使用心得。

 

图2 领域驱动设计倡导者Eric Evans

你可能感兴趣的:(领域驱动设计在大规模项目中的应用心得 --发表于《软件世界》08年第1期)