一、概述
随着系统的增长,它会越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了。
最负雄心的企业欲实现一个涵盖所有业务、紧密集成的系统。因大型公司的业务模型巨大且复杂,很难把它作为一个整体来理解且难以管理。按传统做法,我们可以将其拆为较小的部分后组合。但问题在于,如何保证实现这种模块化的同时,不失去集成所具备的好处;从而使系统的不同部分能够进行互操作,以便协调各种业务操作。如果设计一个把所有概念都涵盖进来的单一领域模型,它将会非常笨拙,而且将会出现大量难以察觉的重复和矛盾。而如果用临时拼凑的接口把一组小的、各自不同的子系统集成到一起,又不具备解决企业级问题的能力,并且在每个集成点上都有可能出现不一致。通过采用系统的、不断演变的设计策略,就可以避免这两种极端问题。
即使这种规模的系统中采用领域驱动设计方法,也不要脱离实现去开发模型。每个决策都必须对系统开发产生直接的影响,否则它就是无关的决策。战略设计原则必须指导设计决策,以便减少各个部分之间的互相依赖,在使设计意图更为清晰的同时而不失去关键的互操作性和协同性。战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景”上。而且在完成这些目标的同时又不能为项目带来麻烦。为了帮助实现这些目标,需探索三个主题:上下文、精炼和大型结构。
1、上下文是最不易引起注意的原则,但实际上它却是最根本的。无论大小,成功的模型必须在逻辑上一致,不能有互相矛盾或重叠的定义。有时,企业系统会集成各种不同来源的子系统,或包含诸多完全不同的应用程序,以至于无法从同一个角度来看待领域。要把这些不同部分中隐含的模型统一起来可能是要求过高了。通过为每个模型显式地定义一个Bounded Context,然后在必要的情况下定义它与其他上下文的关联,建模人员就可以避免模型变得缠杂不清。
2、精炼可以减少混乱,并且把注意力集中到正确的地方。人们通常在领域的一些次要问题上花费太多的精力。整体领域模型需要突出系统中最有价值和最特殊的那些方面,而且在构造领域模型时应该尽可能把注意力集中在这些部分上。虽然一些支持组件也很关键,但绝不能把它们和领域核心一视同仁。把注意力集中到正确的地方不仅有助于把精力投入到关键部分上,而且还可以使系统不会偏离预期方向。战略精炼可以使大的模型保持清晰。有了更清晰的视图后,Core Domain的设计就会发挥更大的作用。
3、大型结构是用来描述整个系统的。在非常复杂的模型中,人们可能会“只见树木,不见森林”。精炼确实有帮助,它使人们能够把注意力集中到核心元素上,并把其他元素表示为支持作用,但如果不贯彻某个主旨来应用一些系统级的设计元素和模式的话,关系仍可能非常混乱。有几种组织大型结构的方法,可以用它们作为基础结构,去构建我们需要的大型结构。如Responsibility Layer(职责层) 、Evolving Order等。
这3种原则各有各的用处,但结合起来使用将发挥更大的力量。大型结构和精炼能够帮助我们理解各个部分之间的复杂关系,同时保持整体视图的清晰。Bounded Context(上下文)能够使我们在不同的部分中进行工作,而不会破坏模型或是无意间导致模型的分裂。把这些概念加进团队的Ubiquitous Language中,可以帮助开发人员找出大型模型的自己的解决方案。
二、保持模型的完整性
模型最基本的要求是它应该操持一致,术语总是具有相同的意义,并且不包含互相矛盾的规则:虽然我们很少明确地考虑这些要求。模型内部的一致性又叫统一(Unification),这种情况下,每个术语都不会有模棱两可的意义,也不会有规则冲突。
但大型系统开发并非如此理想。在整个企业系统中保持这种水平的统一是一件得不偿失的事情。在系统的各个不同部分中开发多个模型是很有必要的,但我们必须慎重地选择系统的哪些部分可以分开,以及它们之间是什么关系。我们需要是一些方法来保持模型关键部分的高度统一。这需要通过有意识的设计决策和建立特定过程才能实现。大型系统领域模型的完全统一即不可行,也不划算。
有时人们会反对这一点,多个模型看上去似乎不够雅致。甚至对多个模型的抵触会导致“极富雄心”的尝试——将一个大型项目的所有软件统一到单一模型中。如果想这样做一定要考虑以下风险。
(1)一次尝试对遗留系统做过多的替换。
(2)大项目可能会陷入困难,因为协调的开销太大,超出了这些项目的能力范围。
(3)具有特殊需要的应用程序可能不得不使用无法充分满足需要的模型,而只能将这些无法满足的行为入到其他地方。
(4)另一方面,试图用一个模型来满足所有人的需求可能会导致模型中包含过于复杂的选择,因而很难使用。
此外,除了技术的因素外,权力上的划分和管理级别的不同也可能要求把模型分开。而且不同模型的出现也可能是团队组织和开发过程导致的结果。因此,即使完全的集成没有来自技术方面的阻力,项目也可能面临多个模型。
既然无维护一个涵盖整个企业的统一模型,那就不要再受到这种思路的限制。通过预先决定什么应该统一,并实际认识到什么不能统一,我们就能够创建一个清晰的、共同的视图。确定了这些之后,就可以开始着手开始工作,以保证那些需要统一的部分保持一致,不需要统一的部分不会引起混乱或破坏模型。
我们需要用一种方式来标记出不同模型之间的边界和关系。我们需要有意识地选择一种策略,并一致地遵守它。
为识别、沟通和选择模型边界及关系,我们使用了一些模式,Bounded Context(限界上下文)定义了每个模型的应用范围,而Context Map(上下文图)则给出了项目上下文以及它们之间关系的总体视图。这些降低模糊性的技术能够使项目更好地进行,但还不够。一旦确立了Context的边界之后,仍需要持续集成这种过程,它能够使模型保持统一。
其后,在这个稳定的基础之上,我们就可以开始实施那些在界定和关联Context方面更有效的策略——从通过共享内核(Shared Kernel)来紧密上下文,到那些各行其道(Separate Ways)地进行松散耦合的模型。
模型完整性模式的导航图
1、模式:Bounded Context
大型项目上会有多个模型共存,在很多情况下这没什么问题。不同的模式应用于不同的上下文中。但也会有引起含糊和混乱的情况。如:两个团队为同一个新系统开发不同的功能,那么他们使用的是同一个模型吗?他们的意图是至少共享其所做的一部分工作,但却没有界限告诉他们共享什么、没共享什么。而且他们也没有一个过程来维护共享模型,或快速检测模型是否有分歧。他们只是在系统行为不可预测时才意识到他们之间产生了分歧。
即使在同一个团队中,也可能会出现多个模型。团队的沟通可能会不畅,导致对模型的理解产生难以捉摸的冲突。原先的代码往往反映的是早先的模型概念,而这些概念与当前模型有着微妙的差别。
每个人都知道两个系统的数据格式是不同的,因此需要进行数据转换,但这只是问题的表面。问题的根本在于两个系统所使用的模型不同。当这种新差异不是来自外部系统,而是发生在同一个系统中时,它将更难发现。然而,所有大型团队项目都会发生这咱情况。
任何大项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现Bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。
模型混乱的问题最终会在代码不能正常运行时暴露出来,但问题的根源却在于团队的组织方式和成员的交流方法。因此,为了澄清模型的上下文,我们既要注意项目,也要注意它的最终产品(代码、数据库模式等)。
一个模型只在一个上下文中使用。这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。如果模型是在一次头脑风暴会议中得到的,那么这个模型的上下文可能仅限于那次讨论。就拿本书来说,示例中所使用的模型的上下文就是那个示例所在的小节以及任何相关的后续讨论。模型上下文是为了保证该模型中的术语具有特定意义而必须要应用的一组条件。
为了解决多个模型的问题,我们需要明确地定义模型的范围——模型的范围是软件系统中一个有界的部分,这部分只应用一个模型,并尽可能保持统一。团队组织必须一致遵守这个定义。
因此:
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
Bounded Context明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在Context中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他Context中,会使用其他模型,这些模型具有不同的术语、概念、规则和Ubiquitous Language的技术行话。通过划定明确的边界,可以使模型保持纯粹,因而在它所适用的Context中更有效。同时,也避免了将注意力切换到其他Context时引起的混淆。跨边界的集成必然需要进行一些转换,但我们可以清楚地分析这些转换。
Bounded Context不是Module,有时这两个概念易引起混淆,但它们是具有不同动机的不同模式。确实,当两组对象组成两个不同模型时,人们几乎总是把它们放在不同的Module中。但人们也会在同一模型中用Module来组织元素,它们不一定要表达划分Context的意图。
识别Bounded Context中的不一致
很多征兆都可能表明模型中出现了差异。最明显的是已编码的接口不匹配。对于更微妙的情况,一些意外行为也可能是一种信号。采用了自动测试的Continuous Integration可以帮助捕捉到这类问题。但语言上的混乱往往是一种早期的警告信号。
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生变化时,都必须更新两个地方。每次由于新知识导致一个对象被修改时,必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一概念的两个版本,它们遵守不同的规则,甚至有不同的数据。更严重的是,团队成员必须学习做同一件事情 的两种方法,以及保持这两种方法同步的各种方式。
假同源可能稍微少见一点,但它潜在的危害更大。它要是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。
当发现这些问题时,团队必须要做出相应的决定。可能需要将模型重新整合为一体,并加强用来预防模型分裂的过程。分裂也有可能是由分组造成的,一些小组出于合理的原因,需要以一些不同的方式来开发模型,而且你可能也决定让他们独立开发。
三、参考文档
DOMAIN-DRIVERN DESIGN
TACKLING COMPLEXITY IN THE HEART OF SOFTWARE
领域驱动设计
软件核心复杂性应对之道
【美】Eric Evans 著 赵俐 盛海艳 刘霞 等 译