本文为领域驱动设计系列总结的第五篇,主要对领域驱动设计概念做个介绍,本系列领域驱动设计总结主要是在Eric Evans 所编写的《领域驱动设计》 一书的基础上进行归纳和总结。本文主要介绍在领域驱动设计中如何设计大型系统。
大型系统往往需要涵盖的业务模型过于复杂,难以管理,如果将业务全部集成则会导致系统过于复杂,难以维护。但如果拆分成一个个小的模块,则失去了集成的好处,且在将各模块集成在一起时容易出现各种问题。
即使在采用领域驱动设计方法,也不能脱离实现去开发模型。每个决策都必须对系统开发产生直接的影响,战略设计原则必须指导设计决策,以便减少各个部分之间的互相依赖,在使设计意图更为清晰的同时而又不失去关键的互操作性和协同性。战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景”上。而且在完成这些目标的同时又不能为项目带来麻烦。战略设计原则一般有上下文,精炼,大型结构三个原则。
一 上下文
上下文是最根本的原则,无论大小,成功的模型必须在逻辑上一致,不能有互相矛盾或重叠的定义。通过为每个模型显式地定义一个清晰的OUNDED CONTEXT,然后在必要的情况下定义它与其他上下文的关系,以避免模型之间变得缠杂不清。
1.1 限界上下文(BOUNDED CONTEXT)
我们可以明确地定义模型所应用的上下文即限界上下文(BOUNDED CONTEXT)。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
限界上下文(BOUNDED CONTEXT)明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在BOUNDED CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他BOUNDED CONTEXT中,会使用其他模型,这些模型具有不同的术语、概念、规则和UBIQUITOUS LANGUAGE的技术行话。通过划定明确的边界,可以使模型保持纯粹,因而在它所适用的BOUNDEDCONTEXT中更有效。同时,也避免了将注意力切换到其他BOUNDED CONTEXT时引起的混淆。跨边界的集成必然需要进行一些转换,但我们可以清楚地分析这些转换。
但将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。假同源是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。
所以我们可以使用持续集成(CONTINUOUS INTEGRATION)的方式把一个限界上下文(BOUNDED CONTEXT)中的所有工作足够频繁地合并到一起,并使它们保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题。
1.2 上下文图(CONTEXT MAP)
识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。先将当前的情况描绘出来。以后再做改变。
模型上下文总是存在的,但如果我们不注意的话,它们可能会发生重叠和变化。通过明确地定义BOUNDED CONTEXT和CONTEXT MAP,团队就可以掌控模型的统一过程,并把不同的模型连接起来。
1.3 限界上下文(BOUNDED CONTEXT)之间联系方式
- 1. 共享内核(SHARED KERNEL),两个BOUNDED CONTEXT提取公共部分,共同维护。
- 2. 客户/供应商关系(CUSTOMER/SUPPLIER DEVELOPMENT TEAM),上下游BOUNDED CONTEXT开发团队之间可以建立一种明确的客户/供应商关系,并以此来进行合作和交互。
- 3. 跟随者(CONFORMIST),下游BOUNDED CONTEXT开发团队严格遵守上游团队模型。
- 4. 防腐层(ANTICORRUPTION LAYER),在两个BOUNDED CONTEXT之间创建一个防腐层,两个BOUNDED CONTEXT通过防腐层来进行转换以便相互联系。
- 5. 各行其道(SEPARATE WAY),完全隔离各BOUNDED CONTEXT,保持彼此独立。
- 6. 开放主机(OPEN HOST SERVICE),对外提供一组协议Service供其他系统访问。
- 7. 已发表语言(PUBLISHED LANGUAGE)使用一些已发布的公共语言进行交互,比如xml,json等语言。
简单来说我们需要创建为每个模型显式地定义一个BOUNDED CONTEXT,将各个模型分割开来,然后再通过各种方式处理各BOUNDED CONTEXT之间的联系,最后将这些BOUNDED CONTEXT全部都集成起来,组成一个CONTEXT MAP,最终构成一个大型系统。
二 精炼
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容,而这种形式将使它更有价值,也更有用。模型就是知识的精炼。通过每次重构所得到的更深层的理解,我们得以把关键的领域知识和优先级提取出来。精炼的主要动机是把最有价值的那部分提取出来,正是这个部分使我们的软件区别于其他软件并让整个软件的构建物有所值,这个部分就是CORE DOMAIN。
领域模型的战略精炼包括以下部分:
(1) 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;
(2) 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通;
(3) 指导重构;
(4) 专注于模型中最有价值的那部分;
(5) 指导外包、现成组件的使用以及任务委派。
2.1 核心领域(CORE DOMAIN)
CORE DOMAIN是系统中最有价值的部分。我们需要对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。
让最有才能的人来开发CORE DOMAIN。在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的CORE。
如何易发现、使用和修改CORE DOMAIN?
第一,选择核心,我们需要关注的是那些能够表示业务领域并解决业务问题的模型部分。对CORE DOMAIN的选择取决于看问题的角度。一个应用程序的CORE DOMAIN在另一个应用程序中可能只是通用的支持组件。人们对CORE DOMAIN的认识也会随着迭代而发展。开始时,一些特定关系可能显得不重要。而最初被认为是核心的对象可能逐渐被证明只是起支持作用。
第二,工作的分配,建立一支由开发人员和一位或多位领域专家组成的联合团队,其中开发人员必须能力很强、能够长期稳定地工作并且对学习领域知识非常感兴趣,而领域专家则要掌握深厚的业务知识。
第三,持续精炼,通过持续对模型进行精炼,使得CORE DOMAIN越来越清晰和突出。
2.2 精炼技巧
- 1. 通用子域(GENERIC SUBDOMAIN),识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独的MODULE中。不要特别关注通用子域的可重用性。
- 2. 领域愿景说明(DOMAIN VISION STATEMENT),写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,展示出领域模型是如何实现和均衡各方利益的。
- 3. 突出核心(HIGHLIGHTED CORE),通过精炼文档,标明CORE,把精炼文档作为过程工具等方式,识别出模型中具体核心元素。
- 4. 内聚机制(COHESIVE MECHANISM)将一些内聚部分提取出来分离到一个单独的轻量级框架中承担起支持的任务,从而留下一个更小的、表达得更清楚的CORE DOMAIN。
- 5. 分离的核心(SEGREGATED CORE)对模型进行重构,把核心概念从支持性元素中分离出来,增强CORE的内聚性,减少它与其他代码的耦合。将所有通用元素或支持性元素提取到其他对象中。
- 6. 抽象内核(ABSTRACT CORE)把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型,使之能够表达出重要组件之间的大部分交互。
以上精炼技巧总体结构如下:
2.3 深层模型精炼
精炼并不仅限于从整体上把领域中的一些部分从CORE中分离出来。它也意味着对子领域进行精炼,通过持续重构得到更深层的理解,从而向深层模型和柔性设计推进。精炼的目标是把模型设计得更明显,使我们可以用模型简单地把领域表示出来。深层模型把领域中最本质的方面精炼成一些简单的元素,使我们可以把这些元素组合起来解决应用程序中的重要问题。
尽管任何带来深层模型的突破都有价值,但只有CORE DOMAIN中的突破才能改变整个项目的轨道。
三 大型结构
大型结构是用来描述整个系统的。在非常复杂的模型中,人们可能会“只见树木,不见森林”。精炼确实有帮助,它使人们能够把注意力集中到核心元素上,并把其他元素表示为支持作用,但如果不贯彻某个主旨来应用一些系统级的设计元素和模式的话,关系仍然可能非常混乱。大型结构能够使设计保持一致性,从而加速开发,并提高集成度。
“大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统。它用一组高级概念或规则(或两者兼有)来为整个系统的设计建立一种模式。这种组织原则既能指导设计,又能帮助理解设计。另外,它还能够协调不同人员的工作,因为它提供了共享的整体视图,让人们知道各个部分在整体中的角色。
大型结构是设计一种应用于整个系统的规则(或角色和关系)模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的位置。
3.1 大型结构设计模式
1. 有序演化(EVOLVING ORDER)
大型结构应该随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。不要依此过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细知识之后才能确定。
2.系统隐喻(SYSTEM METAPHOR)
当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。围绕这个隐喻来组织设计。
3. 职责层(RESPONSIBILITY LAYER)
如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象AGGREGATE和MODULE的职责都清晰地位于一个职责层当中。
4. 知识级别(KNOWLEDGE LEVEL)
创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
5. 可插入式组件框架(PLUGGABLE COMPONENT FRAMEWORK)
从接口和交互中提炼出一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接口进行操作,那么就可以允许它使用这些组件。
大型结构总体结构如下:
3.2 通过重构得到更适当的结构
我们必须在开发过程中进行重构,以便得到最终的结构。可以采用下面一些方法进行重构:
- 最小化,控制成本的一个关键是保持一种简单、轻量级的结构。不要试图使结构面面俱到。只需解决最主要的问题即可,其他问题可以留到后面一个一个地解决。
- 沟通和自律,团队中,仅仅通过沟通是不足以保证在系统中采用一致的大比例结构的。至关重要的一点是要把它合并到项目的通用语言中,并让每个人都严格地使用UBIQUITOUS LANGUAGE。
- 通过重构得到柔性设计,通过不断重构,不断增加的知识被合并到模型中,更改的要点已经被识别出来,并且更改也变得更灵活,同时模型中一些稳定的部分也得到了简化,概念轮廓也更好的显现出来。
- 通过精炼可以减轻负担,持续精炼可以从各个方面减小修改结构的难度。
通过精炼和重构得到更深层理解的原理甚至也适用于大比例结构本身。例如,最初可以根据对领域的初步理解来选择分层结构,然后逐步用更深层次的抽象来代替它们。这种极高的清晰度使人们能够透彻地理解领域,这也是我们的目标。它也是一种使系统的整体控制变得更容易、更安全的手段。
上下文,精炼,大型结构这三种原则各有各的用处,但结合起来使用将发挥更大的力量,遵守这些原则就可以创建出好的设计,即使是对一个非常庞大的没有人能够完全理解的系统也是如此。大型结构能够保持各个不同部分之间的一致性,从而有助于这些部分的集成。结构和精炼能够帮助我们理解各个部分之间的复杂关系,同时保持整体视图的清晰。BOUNDED CONTEXT使我们能够在不同的部分中进行工作,而不会破坏模型或是无意间导致模型的分裂。把这些概念加进团队的UBIQUITOUS LANGUAGE中,可以帮助开发人员找出他们自己的解决方案。
上下文,精炼,大型重构三原则总体结构如下: