在一个大的、复杂的系统中,可能需要在一个设计中综合运用几种策略。那么,大型结构如何与CONTEXT MAP共存?应该把构造块放到哪里?第一步先做什么?第二步和第三步呢?如何设计你的战略?
把大型结构与BOUNDED CONTEXT结合起来使用
战略设计的3个基本原则(上下文、精炼和大型结构)并不是可以互相代替的,而是互为补充,并且以多种方式进行互动。例如,一种大型结构可以存在于一个BOUNDED CONTEXT中,也可以跨越多个BOUNDED CONTEXT存在,并用于组织CONTEXT MAP。
前面的RESPONSIBILITY LAYER的例子被限定在一个BOUNDED CONTEXT中。这是解释这一思想的最简单的方法,也是该模式的一般用法。在这样的简单场景中,层名称的含义仅用于该CONTEXT,该CONTEXT中的模型元素或子系统接口的名称也是如此。
这样的局部结构在一个非常复杂但统一的模型中是很有用的,它使系统所能承受的复杂度上限提高了,进而使得在一个BOUNDED CONTEXT中可以维护更多的对象。
但是在很多项目中,更大的挑战是理解怎样使各个不同的部分构成一个整体,如下图所示:
这些部分可能被划分到不同的BOUNDED CONTEXT中,但是各个部分在整个集成系统中的作用是什么,它们之间又是如何互相关联的?理解了这些问题之后就可以用大型结构来组织CONTEXT MAP。在这种情况下,结构的术语适用于整个项目(或至少是项目中某个明确限定的部分)。
假设你打算采用RESPONSIBILITY LAYER模式,但你有一个遗留系统,它的组织结构与你想要采用的大型结构不一致。那么是否必须放弃LAYERS模式?不必,但是你必须确定遗留系统在新结构中的位置,如下图所示:
实际上,RESPONSIBILITY LAYER可能有助于刻画遗留系统的特征。遗留系统所提供的SERVICE可以被限定到几个层中。如果我们能够说出遗留系统与哪几个特定的RESPONSIBILITY LAYER相符,那么这就非常精确地描述了遗留系统的范围和角色的关键方面。
如果遗留子系统的功能是通过一个FACADE来访问的,那么设计时,该FACADE所提供的每个SERVICE应该只在一个层中,不跨越多个层。
在这个示例中,Shipping Coordination应用程序是一个遗留系统,它的内部机制是作为一个无差别的整体呈现出来的。但如果项目团队已经很好地建立了一种跨CONTEXT MAP的大型结构,那么团队可以选择在他们的CONTEXT中按照已经熟悉的层来组织模型,如下图所示:
当然,由于每个BOUNDED CONTEXT都是其自己的命名空间,因此在一个CONTEXT中可以使用一种结构来组织模型,而在相邻的CONTEXT中则可以使用另一种结构,然后再使用一种别的结构来组织CONTEXT MAP。但是,使用过多的结构会损害大型结构作为项目统一概念集的价值。
将大型结构与精炼结合起来使用
大型结构和精炼的概念也是互为补充的。大型结构可以帮助解释CORE DOMAIN内部的关系以及GENERIC SUBDOMAIN之间的关系。
同时,大型结构本身可能也是CORE DOMAIN的一个重要部分。例如,把潜能层、作业层、策略层和决策支持层区分开,能够提炼出对软件所要解决的业务问题的基本理解。当项目被划分为多个BOUNDED CONTEXT时,这种理解尤其有用,这样CORE DOMAIN的模型对象就不会具有过多的含义。
首先评估
当对一个项目进行战略设计时,首先需要清晰地评估现状。
(1) 画出CONTEXT MAP。你能画出一个一致的图吗?有没有一些模棱两可的情况?
(2) 注意项目上的语言使用。有没有UBIQUITOUS LANGUAGE?这种语言是否足够丰富,以便帮助开发?
(3) 理解重点所在。CORE DOMAIN被识别出来了吗?有没有DOMAIN VISION STATEMENT?你能写一个吗?
(4) 项目所采用的技术是遵循MODEL-DRIVEN DESIGN,还是与之相悖?
(5) 团队开发人员是否具备必要的技能?
(6) 开发人员是否了解领域知识?他们对领域是否感兴趣?
当然,我们不会发现完美的答案。我们现在对项目的了解永远不如将来的了解深入。但这些问题为我们提供了一个可靠的起点。当知道了这些问题的初步答案后,我们就会明白什么是最迫切需要解决的。随着时间的推进,我们可以得出更精炼的答案,特别是CONTEXT MAP、DOMAIN VISION STATEMENT,以及其他创建出来的工件,这些答案都反映出了变化的情况和新的理解。
由谁制定策略
传统上,架构是在应用程序开发开始之前建立的,并且在这种组织中,负责建立架构的团队比应用开发团队拥有更大的权力。但我们并不一定得遵循这种传统的方式,因为它并不总是十分有效。
战略设计必须明确地应用于整个项目。项目有很多组织方式。但是,要想使决策制定过程更有效,需要注意一些基本问题。
首先,我们简单介绍一下两种在实践中具有一定价值的风格(摒弃了传统的"由高层制定决策"的做法)。
从应用程序开发自动得出的结构
一个非常善于沟通、懂得自律的团队在没有核心领导的情况下照样能够很好地工作,他们能够遵循EVOLVING ORDER来达成一组共同遵守的原则,这样就能够有机地形成一种秩序,而不用靠命令来约束。
这是极限编程团队的典型模式。从理论上讲,任何一对编程人员都可以根据自己的理解来完全自发地创建一种结构。通常,让团队中的一个人(或几个人)来承担大型结构的一些监管职责有利于保持结构统一。如果这位承担监管职责的非正式的领导人也是一位负责具体工作的开发人员(仲裁者和协调员),而不是决策的唯一制定者,那么这种方法将特别有效。这样的策略设计领导者可能会自动出现,而且通常在教练中产生。不管这个自动出现的领导人是谁,他仍然是开发团队的成员之一。由此可见,开发团队必须至少有几位具有这样才干的人,由他们来制定一些运用到整个项目中的设计决策。
当多个团队使用同一种大型结构时,密切相关的团队可以开始非正式的协作。在这种情况下,对这种大型结构,每个应用程序团队仍会产生各自的想法,而其中一些具体选择会由一个非正式的委员会来讨论,这个委员会由各个团队的代表组成。在评估了这些选择对设计的影响之后,委员会决定是采用它、修改它,还是放弃它。团队在这种松散的合作关系下一起前进。这种安排要想发挥作用,需要保证:团队数目相对较少,各个团队之间能够一致地保持彼此协调,他们的设计能力大致相同,而且他们的结构需求基本一致,可以通过同一种大型结构来满足。
以客户为中心的架构团队
当几个团队共用同一种策略时,确实需要集中制定一些决策。架构师如果脱离实际开发工作,就可能会设计出失败的模型,但这是完全可以避免的。架构团队可以把自己放在与应用开发团队平等的位置上,帮助他们协调大型结构、BOUNDED CONTEXT边界和其他一些跨团队的技术问题。为了在这个过程中发挥作用,架构团队必须把思考的重点放在应用程序的开发上。
在组织结构图中,这样的团队看起来与传统的架构团队没什么分别,但实际上二者在每一项活动中都存在不同。架构团队的成员是真正的开发协作者,他们与开发人员一起发现模式,与各个团队一起通过反复实验进行精炼,并亲自动手参与开发工作。
制定战略设计决策的6个要点
决策必须传达到整个团队
显然,如果不能确保团队中的所有人都知道策略并去遵守它,那么策略也就失去了作用。这个要求引导人们以架构团队(具有正式的"权威")为中心组织到一起,以便在整个项目中应用一致的规则。然而具有讽刺意味的是,那些脱离实际开发工作的架构师往往会被人们忽略或躲开。如果架构师没有实践经验,又试图把他们自己的规则强加于实际的应用程序,那么他们所设计出来的模式就会不切实际,这时开发人员除了忽略他们之外别无选择。
在一个沟通良好的项目中,应用开发团队所产生的策略设计实际上会更有效地传播到每个人。这样策略将会实际发挥作用,而且具有权威性,因为它是通过集体智慧制定的决策。
无论开发什么系统,都不要用管理层所授予的权力来强制地推行战略决策,而应该更多地关注开发人员与策略之间的实际关系。
决策过程必须收集反馈意见
无论是建立组织原则、大型结构还是那些微妙的精炼,都需要真正理解项目的需求和领域概念。那些唯一具有这方面深层次知识的人就是应用程序开发团队的成员。这解释了为什么架构团队所创建的应用架构很少对项目产生帮助,尽管我们必须承认很多架构师都非常有才能。
与技术基础设施和架构不同,战略设计虽然影响到所有的开发工作,但是它本身并不需要编写很多代码。战略设计真正需要的是应用开发团队的参与。经验丰富的架构师可以听取来自各个团队的想法,并促进总体解决方案的开发。
计划必须允许演变
有效的软件开发是一个高度动态的过程。如果最高层的决策已经固定下来,那么当团队需要对变更做出响应时,选择就会更少。遵循EVOLVING ORDER这一原则,可以避免出现这个问题,因为它强调的是根据理解的不断加深来调整大型结构。
当很多设计决策过早地固定下来时,开发团队可能会束手束脚,失去解决问题的灵活性。因此,虽然那些为了协调项目而制定的原则可能很有价值,但原则必须能够随着项目开发生命周期的进行而完善和变化,而且不能过分限制应用程序开发人员的能力,因为开发工作本来就已经很难了。
有了积极的反馈之后,当构建应用程序的过程中遇到障碍或是出现了意想不到的机会时,创新就自然而然地涌现出来了。
架构团队不必把所有最好、最聪明的人员都吸收进来
架构层次的设计确实需要技术精湛的人员,而这样的人员总是供不应求。项目经理往往会把那些最有技术天分的开发人员调到架构团队和基础设施团队中,因为他们想要充分利用这些高级设计人员的技能。在项目经理看来,开发人员都希望提高自己的影响力,或是攻克那些"更有趣"的问题。而且,加入精英团队本身也会赢得威望。
这样往往会把那些技术能力较差的人留下来构建应用程序。但要想开发出优秀的应用程序,是需要设计技巧的,因此这样安排注定会造成项目失败。即使战略团队建立了一个很好的战略设计,应用程序开发团队也没有能力把它实现出来。
相反,架构团队几乎从来不会把那些缺乏设计技巧但精通领域知识的开发人员吸纳进来。战略设计并不是一项纯粹的技术任务,把那些精通深层次领域知识的开发人员排除在外只会使架构师的工作更难进行。而且同样也需要领域专家的参与。
所有应用程序团队都应该有一些技术能力很强的设计人员,而且任何从事战略设计的团队也都必须具有领域知识,这两者都是非常重要的。聘用更多高级设计人员是很有必要的,而且使架构团队偶尔从事一下开发工作也会很有帮助。任何有效的战略团队必须要与一个有效的应用程序团队通力合作。
战略设计需要遵守简约和谦逊的原则
任何设计工作都必须精炼而简约,而战略设计尤为需要简约。即使是一个非常小的设计失误也有可能会变成可怕的隐患。把架构团队单分出来时要格外慎重,因为他们将更少感知他们为应用程序开发团队所设置的障碍。同时,架构师对其主要职责的过度关注会使他们迷失方向。有了一个好的想法后,又会引出另一个想法,想法太多最后就会得到一个过度设计的架构,这种体系结构反而起到了负面作用。
相反,我们必须严格地约束自己,从而使设计出来的组织原则和核心模型精简到只包含那些能够显著提高设计清晰度的内容。事实上,几乎任何事物都会对其他某个事物构成障碍,因此每个元素都必须是确实值得存在的。我们需要有一个谦逊的态度,才能认识到我们自己认为的最佳思路可能会妨碍其它人。
对象的职责要专一,而开发人员应该是多面手
良好的对象设计的关键是为每个对象分配一个明确且专一的职责,并且把对象之间的互相依赖减至最小。人们有时会试图让团队中的交流像软件中的交互那样整齐。其实在一个优秀的项目中,会有很多人参与其他人的事情。开发人员有时也处理框架,而架构师有时也会编写应用程序代码。所有人员都可以互相交流。这看似混乱但却行之有效。因此,应该让对象职责专一,而让开发人员成为多面手。
把战略设计与其他设计区分开,是为了帮助澄清所涉及的工作,但必须指出:这两种设计活动并不意味着有两种人员。虽然基于深层模型创建柔性设计是一种高级设计活动,但细节问题也至关重要,因此战略设计工作必须由接触编码工作的人来完成。战略设计源自应用设计,然而战略设计需要一个总体的开发活动视图,这个视图可能跨越多个团队。人们总喜欢想出各种办法把工作分得很细,以使得设计专家不必了解业务,而领域专家也不用知道技术。确实,一个人能学的知识是有限的,但过于专业化也会削弱领域驱动设计的力量。
技术框架同样如此
技术框架提供了基础设施层,从而使应用程序不必自己去实现基础服务,而且技术框架还能帮助把领域与其他关注点隔离开,因此它能够极大地加速应用程序(包括领域层)的开发。但技术框架也是有风险的,那就是它会影响领域模型实现的表达能力,并妨碍领域模型的自由改变。
甚至当框架设计人员并没有特意去干涉领域层或应用层的时候,情况同样如此。用于克服战略设计缺点的原则同样适用于技术架构。遵守演变、简约等原则并且让应用程序开发团队参与进来,就能够得到一组持续精化的服务和规则,这些服务和规则能够真正有助于应用程序的开发,而不会妨碍开发。如果架构不按照这种方式来做,那么它们要么会抑制应用程序开发的创造力,要么会被人们绕过去,从而导致应用程序为了能够把开发进行下去而根本不使用架构。有一种态度肯定会使框架流于失败。
不要编写"傻瓜式"的框架
在划分团队时,如果认为一些开发人员不够聪明,无法胜任设计工作,而让他们去做开发工作,那么这种态度可能会导致失败,因为他们低估了应用程序开发的难度。如果这些人在设计方面不够聪明,就不应该让他们来开发软件。如果他们足够聪明,那么这种隔离只会造成障碍,使他们得不到所需的工具。
注意,把无关的技术细节封装起来与"傻瓜式"的预打包完全不同。框架可以为开发人员提供有力的抽象和工具,使他们不用去做那么多苦差事。有用的封装和"傻瓜式"的预打包之间的区别很难用一种通用的方式描述出来,但只要问问框架设计人员他们对将要使用工具/框架/组件的那些人有什么期望,就可以看出区别。如果设计人员对框架的用户非常尊重,那么他们的工作方向可能就是正确的。
注意总体规划
总体规划试图建立足够多的指导方针,来保持整体环境的一致性,同时仍然为每个局部保留自由度,并为适应局部需要预留下广阔的空间。
通过总体规划是无法得到一种有机的秩序的,因为这个规划既过于精确,又不够细致。它在整体上过于精确了,而在细节上又不够细致。
参考
《领域驱动设计 软件核心复杂性应对之道》 Eric Evans 著, 赵俐 盛海艳 刘霞 等译, 任发科 审校