何为“领域驱动设计”
1. 为什么领域?做好软件必须理解其对应的业务的领域,谁最了解对应的业务的领域?那个行业的客户。所以好的软件必须要实现业务领域的最重要的核心概念和元素,并精确实现它们之间的关系。这需要对领域进行建模。
2. 怎样领域?我们要对领域进行抽象,它是一个关于领域的模型,它不是一幅图,但它是那幅图表达的思想;也不是那个领域专家头脑中的知识,而是一个经过严格组织并进行选择性抽象的知识。
3. 业务领域中有巨量信息,我们需要组织它,将其系统化,把它分割成小块,每次只处理一块儿。我们需要忽略领域中的很多部分,因为领域包含了如此之多的信息,不能一下子放到一个模型中。而且,它们当中的很多部分我们也不必去考虑。这对它自身而言也是一个挑战:要保留哪些内容放弃哪些内容呢?这在软件建立过程中属于设计部分的范畴。
4. 模型是软件设计中最基础的部分,我们需要用模型来交流。
5. 当有一个表现的模型时,就可以开始做代码设计了。代码设计(细节工作)与软件设计(总架构相关)不同。(这一点不明白为什么)
6. 瀑布模型有的缺点是交流是单向的。
7. XP可以解决的一个问题是“分析瘫痪”(团队成员因为害怕做出设计决定而无所事事)。XP承认设计的重要性,但反对预先设计。
8. XP的缺点:缺乏真实可见的设计原则,持续的重构会导致代码更难理解或改变。
9. 通过与业务专家交流尽力判断出领域的基础性概念,这是起点,也非常重要。
通用语言
1. 领域驱动设计的一个核心原则是使用一种基于模型的语言。使用模型作为语言的核心骨架,这种语言叫“通用语言”。通用语言连接起设计中的所有部分。
2. 如果业务专家不能理解模型或语言中的某些内容,那么就说明其中存在某种错误。另一方面,开发人员应该留意那些与他们试图呈现在设计中的内容存在不一致的部分。
3. 由于建立通用语言的过程中困难很多,为了解决这一点,要求所有的团队成员应该都意识到需要建立一种通用的语言,并应保持专注核心部分,在任何需要之时使用通用语言。尽可能少地与业务专家使用我们自己的行话。
4. 强烈建议开发人员把这些模型中的主要概念实现到代码中。通过为模型概念建立对应时刻关注企业软件开发领域的变化与创新的类,我们在模型和代码之间以及在语言和代码之间做映射。这非常有帮助,它会让代码更可读,让模型得到完美实现。代码表现模型会让项目得益,如果代码没有得到适当地设计,在模型变大或代码中发生了变化时,会导致意料之外的结果。
5. 以UML 不能传达一个模型很重要的两个方面:它所要表现的概念的意义和对象准备做什么。我们可以使用文档。一个明智的沟通模型的方式是创建一些小的图,让每副小图包含模型的一个子集。这些图会包含若干类以及它们之间的关系。这样就很好地包括了所涉及到的概念中一部分。然后我们可以向图中添加文本。文本将解释图所不能表现的行为和约束。每个这样的片断都试图解释领域中的一个重要的方面,类似于一个聚光灯那样只聚焦领域的一个部分。
6. 冗长的文档,会让我们花费很多的时间来书写它们,有可能在完成之前它们就已经作废了.使用了错误的语言、或者不能如实反映模型,都是没有什么用的。应尽可能的避免这样文档(现在我们公司的做法这个问题就特别严重)。
模型驱动设计
1.
当业务模型建立以后,如何能保证代码能完整准确地反应模型?
分析模型不能解决这个问题,因为它在分析业务产生模型过程中不考虑软件如何实现,它没有涉及到设计原则,因此开发人员在这种情况下就不得不修改它,或建立分离的设计。这样就使模型和代码之间的映射存在误差。分析模型的一个主要问题是分析不能预见模型中存的某些缺陷及领域中的所有的复杂关系,非常重要的细节直到实现过程中才被发现。如果分析人员独立工作,他们将模型传递给开发人员时,一些领域知识和模型就丢失了,而且开发人员需要花费大量的精力来搞明白去理解模型文档。
一个比较好的办法是建模时开发人员就加入进来。任何技术人员想对模型做出贡献必须花一些时间来接触代码,每一位开发人员都必须参与到一定级别的领域讨论中并和领域专家联络,这个过程中必须使用通用语言。
2. 在设计系统时,将领域层独立出来:
将程序划分成分离的层并建立层间的交换规则很重要。如果代码没有清晰地隔离到某层中,它会变得非常难以管理变更。
实体
1. 什么是实体?有一类对象看上去好像拥有标识符,它的标识符在历经软件的各种状态后仍能保持一致,这意味着能够跨越系统的生命周期甚至能超越软件系统的一系列的延续性和标识符。我们把这样的对象称为实体。
2. 在软件中实现实体意味着创建标识符。对两个拥有不同标识符的对象来说,能用系统轻易地把它们区分开来,或者使用了相同标识符的对象能被系统看成是相同的。
3. 实体是领域模型中非常重要的对象,并且它们应该在建模过程开始时就被考虑。
值对象
1. 用来描述领域的特殊方面、且没有标识符的一个对象,叫做值对象。
2. 如果值对象是可共享的,那么它们应该是不可变的。
3. 被选择用来构成一个值对象的属性应该形成一个概念上的整体。一个客户会跟其名称、街道、城市、州县相关。最好分离出一个对象来包含地址信息,客户对象会包含一个对地址对象的引用。
服务
1. 当我们分析领域并试图定义构成模型的主要对象时,我们发现有些方面的领域很难映射成对象。对象要通常考虑的是拥有属性,对象会管理它的内部状态并暴露行为。在我们开发通用语言时,领域中的主要概念被引入到语言中,语言中的名词很容易被映射成对象。语言中对应那些名词的动词变成那些对象的行为。但是有些领域中的动作,它们是一些动词,看上去却不属于任何对象。它们代表了领域中的一个重要的行为,所以不能忽略它们或者简单的把它们合并到某个实体或者值对象中。给一个对象增加这样的行为会破坏这个对象,让它看上去拥有了不应属于它的功能。通常这种行为的功能会跨越若干个对象,或许是不同的类。例如,为了从一个账户向另一个账户转钱,这个功能应该放到转出的账户还是在接收的账户中?感觉放在这两个中的哪一个也不对劲。当这样的行为从领域中被识别出来时,最佳实践是将它声明成一个服务,这样的对象不再拥有内置的状态。服务所能提供的协调作用是非常重要的,一个服务可以将服务于实体和值对象的相关功能进行分组。最好显式声明服务,因为它创建了领域中的一个清晰的特性,它封装了一个概念。
2. 服务担当了一个提供操作的接口的角色。
3.
以下是服务的3个特征:
A.服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象。
B.被执行的操作涉及到领域中的其他的对象。
C.操作是无状态的。
模块
1.
对一个大型的复杂项目而言,模型趋向于越来越大。模型到达了一个作为整体很难讨论的点,理解不同部件之间的关系和交互变得很困难。基于此原因,很有必要将模型进行模块化。
另一个使用模块的原因跟代码质量有关。普遍认为软件代码应该具有高内聚性和低耦合度。强烈推荐将高关联度的类分组到一个模块以提供尽可能大的内聚。
2. 最常用到的两种类型的内聚是通信性内聚和功能性内聚。通信性内聚通常在模块的部件操作相同的数据时使用。把它们分到一组很有意义,因为它们之间存在很强的关联性。功能性内聚在模块中的部件协同工作以完成定义好的任务时使用。这被认为是最佳的内聚类型。
3. 模块应该具有定义好的接口,这些接口可以被其他的模块访问。最好用访问一个接口的方式替代调用模块中的三个对象,因为这可以降低耦合。
聚合
1. 聚合是一个用来定义对象所有权和边界的领域模式。聚合是针对数据变化可以考虑成一个单元的一组相关的对象。。聚合使用边界将内部和外部的对象划分开来。每个聚合有一个根。这个根是一个实体,并且它是外部可以访问的唯一的对象。根可以保持对任意聚合对象的引用,并且其他的对象可以持有任意其他的对象,但一个外部对象只能持有根对象的引用。如果边界内有其他的实体,那些实体的标识符是本地化的,只在聚合内有意义。
2. 聚合是如何保持数据一致性和强化不变量的呢?因为其他对象只能持有根对象的引用,这意味着它们不能直接变更聚合内的其他的对象。它们所能做的就是对根进行变更,或者让根来执行某些活动。根能够变更其他的对象,但这是聚合内包含的操作,并且它是可控的。
3. 如果聚合对象被保存到数据库中,只有根可以通过查询来获得。其他的对象只能通过导航关联来获得。
4. 根对象可能将内部的临时引用传递给外部对象,一个简单的实现方式是向外部对象传递一个值对象的拷贝,临时对内部成员的引用仅可以被传递给一个单独的操作使用,当操作完成后,外部对象不能再持有这个引用。在这个对象上发生了什么将不再重要,因为它不会以任何方式影响到聚合的一致性。
工厂
1. 工厂用来封装对象创建所必需的知识,它们对创建聚合特别有用,念可以帮助封装复杂的对象创建过程。
资源库
1. 资源库,它的目的是封装所有获取对象引用所需的逻辑。领域对象不需处理基础设施,以得到领域中对其他对象的所需的引用。从资源库中获取模型所需的引用,于是模型就可以获得它应有的清晰和焦点。
2. 资源库会保存对某些对象的引用。当一个对象被创建出来时,它可以被保存到资源库中,然后以后使用时可从资源库中检索到。如果客户程序从资源库中请求一个对象,而资源库中并没有它,就会从存储介质中获取它。换种说法,资源库作为一个全局的可访问对象的存储点而存在。
资源库可能包含用来访问基础设施的细节信息,但它的接口应非常简单。资源库应该拥有一组用来检索对象的方法。客户程序调用这样的方法,传递一个或者多个代表筛选条件的参数用来选择一个或者一组匹配的对象。资源库的接口是纯粹的领域模型。
3. 工厂和资源库之间存在一定的关系。它们都能帮助我们关联领域对象的生命周期。然而工厂关注的是对象的创建,而资源库关心的是已经存在的对象。工厂创建新的对象,而资源库应该是用来发现已经创建过的对象。当一个新对象被添加到资源库时,它应该是先由工厂创建过的,然后它应该被传递给资源库以便将来保存它,见下面的例子:
面向深层理解的重构
持续重构
1. 模型需要跟它所源自的领域紧密关联,代码设计应该围绕模型展开。模型自身也会基于代码设计决定而有所增进。脱离了模型的代码设计会导致软件不能反映它所服务的领域,甚至可能得不到期望的行为。建模如果得不到代码设计的反馈或者缺少了开发人员的参与,会导致实现模型的人很难理解它,并且可能对所用的技术不太适合。
2. 一个好的模型产生于深层的思考、理解、经验和才能。我们被教授的关于建模的第一件事是阅读业务规范,从中寻找名词和动词。名词被转换成类,而动词则成为方法。这是一种简化,将产生浅层次的模型。所有的模型开始时都缺乏深度,但我们可以面向越来越深的理解来重构模型。
3. 使用经过验证的基础构造元素并使用一致的语言将给开发工作带来成效。这会减少不良模型所带来的挑战,我们可以捕获到领域专家非常关心的内容,并且以此来驱动实际的设计。一个忽略表面内容且捕捉到本质内涵的模型是一个深层模型。这会让软件更加和领域专家的思路合拍,也更能满足用户的需要。
凸现关键概念
1. 为达到一次突破,我们需要让隐式的概念显式化。当我们跟领域专家交谈时,我们交互了很多的思想和知识。某些概念成为了通用语言,但也有些一直未被重视。它们是隐式概念,用来解释以及在领域中的其他的概念。在精化设计的过程中,某些隐式概念吸引了我们注意力。我们发现它们当中的某些在设计中担当了重要的角色。因此我们需要将这些概念显式化。我们应该为它们建立类和关系。当它们发生时,我们就拥有了突破的机会。
2. 隐式概念可能不会仅于此。如果它们是领域概念,它们可能被引入模型和设计中。我们应该如何识别它们呢?
第一种发现隐含概念的方式是倾听业务专家的话语。
第二种是:有时设计的部分可能不会那么清晰,一堆关系会让计算的路径变得难以进行或计算的过程复杂到难以理解。这在设计中显得极其笨拙,但这也是寻找隐藏的概念的绝佳之所,可能我们错过了什么。如果某个关键概念在破解谜团时缺失了,其他的事物就不得不替代它完成它的功能。这会让某些对象变胖,给它们增加了一些本不应该属于它的行为。设计的清晰度受到了破坏。努力寻找是否有缺失的概念,如果找到一个,就把它显式化。对设计进行重构让它更简单更具柔性。
3. 在让概念显式化时,还有其他一些非常有用的概念:约束、过程。
A、约束是一个很简单的表示不变量的方式。无论对象的数据发生了变化,都要考虑不变量。这可以简单地通过把不变量的逻辑放置在一个约束中去实现它。下面是一个简单的案例
我们可以向一个书架添加书籍,但是我们永远不能增加超过它容量的部分。这儿可以被视为书架行为的一部分,见下面的java 代码。
我们可以重构这个代码,将约束提取为一个单独的方法。
将约束置于一个单独的方法让它显示化有很多优点。它很容易阅读,并且每个人都会注意到add()方法从属于这个约束。如果约束变得更复杂,这可以为向该方法增加更多逻辑。
B、 过程,即在结构化编程语言中的procedure。我们需要为过程选择一个对象,然后给该对象增加行为。最好的实现过程的方式是使用服务。如果通用语言中提到了某个过程,那就是将它显式实现的时机了。
保持模型一致性
1. 本章涉及的是需要若干个团队通力配合的大型项目。
2. 其中的一个团队创建了一个模块,然后提供给其他的团队使用。某个团队的开发人员开始在自己的模块中使用这个模块,但发现还缺少一些功能,于是他增加了这个功能并放到代码库里面,以便所有的人都能使用。但是他也许没有意识到,这其实是对模型的一个变更,这个变更很有可能破坏系统的功能。
3. 模型的首要需求是一致性,条款统一和没有矛盾。一个企业项目应该有一个模型,涵盖企业的整个领域,没有矛盾和重叠的条款。统一的企业模型是不容易实现的理想状态,有时甚至都不值得尝试。当模型的设计开始部分独立时,我们就开始面临失去模型完整性的可能性。试图为整个企业项目维持一个大的统一模型以获得模型完整性的做法,将不会有什么作为。我们应该做的是有意识地将大模型分解成数个较小的部分。只要遵守相绑定的契约,整合得好的小模型会越来越有独立性。每个模型都应该有一个清晰的边界,模型之间的关系也应该被精确地定义。
界定上下文
1. 每一个模型都有一个上下文,当我们开发大的企业应用时,需要为每一个我们创建的模型定义上下文。
2. 如何把一个大的模型分解成小的部分没有什么具体的公式。尽量把那些相关联的以及能形成一个自然概念的因素放在一个模型里。模型应该足够小,以便能分给一个团队去实现。模型的上下文是一个条件集合,用这些条件可以确保应用在模型里的条款都有一个明确的含义。
持续集成
1. 一旦界定的上下文被定义,我们就必须保持它的完整性。但多人工作于同一个界定的上下文时,模型很容易被分解,模型被破坏成更小的上下文后,基本上也就失去了完整性和一致性的价值。团队越大,问题越大,不过通常只有三四个人会遇到严重的问题。因此,在团队内部我们需要充分的沟通,以确保每个人都能理解模型中每个部分所扮演的角色。
2. 模型不是一开始就被完全定义。这意味着新的概念会进入模型,新的部分也会被增加到代码中。这也就是为什么持续集成在界定的上下文中如此必要的原因。我们需要一个集成的过程,以确保所有新增的部分和模型原有的部分配合得很好,在代码中也被正确地实现。我们有个合并代码的过程,合并得越早越好。对小的独立团队,推荐每日合并。合并的代码需要自动地被构建,以被测试。
3. 持续集成是基于模型中概念的集成,然后再通过测试实现。任何不完整的模型在实现过程中都会被检测出来。持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。
上下文映射
一个企业应用有多个模型,每个模型有自己的界定的上下文。每个团队都工作于自己的模型,所以最好让每个人都能了解所有的模型。上下文映射(Context Map)是指抽象出不同界定上下文和它们之间关系的文档。它的重要之处是让每个在项目中工作的人都能够得到并理解它。
只有独立的统一模型还不够。它们还要被集成,因为每个模型的功能都只是整个系统的一部分。在最后,单个的部分要被组织在一起,整个的系统必须能正确工作。如果上下文定义的不清晰,很有可能彼此之间互相覆盖。如果上下文之间的关系没有被抽象出来,在系统被集成的时候它们就有可能不能工作。
每个界定的上下文都应该有一个作为Ubiquitous Language 一部分的名字。这在团队之间沟通整个系统的时候非常有用。每个人也应该知道每个上下文的界限以及在上下文和代码之间的映射等。一个通常的做法是先定义上下文,然后为每个上下文创建模型,再用一个约定的名称指明每个模型所属的上下文。
在接下来的章节中,我们要讨论不同上下文之间的交互。在上下文之间,共享内核(Shared Kernel)和客户-供应商(Customer-Supplier)是具有高级交互的模式。隔离通道(Separate Way)是在我们想让上下文高度独立和分开运行的时候要用到的模式。开放主机服务(Open Host Service)和防崩溃层(Anticorruption Layer)两个模式是用来处理系统和继承系统或者外部系统之间的交互的。
共享内核
两个团队中有可能出现一个共同的东西,如果没有共享,就会造成重复劳动,也没有体味到通用语言带来的好处。因此,需要指派两个团队同意共享的领域模型子集。当然除了模型的子集部分,还要包括代码子集。这个明确被共享的东西有特别的状态,没有团队之间的沟通不能做修改。
要经常整合功能系统,在集成的时候,在两个团队里都要运行测试。
共享内核的目的是减少重复,但是仍保持两个独立的上下文。内核的任何改变都应该通知另一个团队,团队之间密切沟通,使大家都能了解最新的功能。
客户-供应商
我们经常会遇到两个子系统之间关系特殊的时候:一个严重依赖另一个。两个子系统所在的上下文是不同的,而且一个系统的处理结果被输入到另外一个。它们没有共享的内核。
客户团队应该代表系统的需求,而供应商团队据此设置计划。当客户团队所有的需求都被激发出来后,供应商团队就可以决定实现它们的时间表。如果认为一些需求非常重要,那么应该先实现它们,延迟其他的需求。客户团队还需要输入和能被供应商团队分享的知识。
两个子系统之间的接口需要预先明确定义。另外还要创建一个统一的测试集,在任何接口需求被提出的时候用于测试。将这些测试增加到供应商团队的测试集里,作为它的持续集成的一部分运行。供应商团队可以在他们的设计上大胆地工作,因为接口测试集的保护网会在任何有问题的时候报警。
在两个团队之间确定一个明显的客户/供应商关系。在计划场景里,让客户团队扮演和供应商团队打交道的客户角色。为客户需求做充分的解释和任务规划,让每个人理解相关的约定和日程表。
顺从者
有很多时候,供应商团队不能完成或不愿意完成与客户团队之间的接口部分,这时客户团队的工作会十分被动。
这时客户团队没有多少选择。最常见的现象是将它从供应商那儿分割出来,自己完全独立,运用“分割方法”。
有时在供应商模型里会有些数据,这时不得不维持一个连接。但是因为供应商团队不帮助客户团队,所以后者也不得不采取一些措施保护自己,以防止前者对模型所做更改带来的影响。他们需要实现连接两边上下文的转换层。也有可能供应商团队的模型没有被很好地理解,效用发挥不出来。虽然客户上下文仍然可以使用它,但是它应该通过使用一个“防崩溃层”来保护自己。
如果客户不得不使用供应商团队的模型,而且这个模型做得很好,那么就需要顺从了。客户团队遵从供应商团队的模型,完全顺从它。这和共享内核很类似,但有一个重要的不同之处。客户团队不能对内核做更改。他们只能用它做自己模型的一部分,可以在所提供的现有代码上完成构建。当有人提供一个丰富的组件,并提供了相应的接口时,我们就可以将这个组件看作我们自己的东西构建我们的模型。如果组件有一个小的接口,那么最好只为它简单地创建一个适配器,在我们的模型和组件模型之间做转换。这会隔离出我们的模型,可以有很高的自由度去开发它。
防崩溃层
这一部分非常精彩,全部抄下来。
我们会经常遇到所创建的新应用需要和遗留软件或者其他独立应用相交互的情况。对领域建模器而言,这又是一个挑战。很多遗留应用从前没有用领域建模技术构建,而且它们的模型模糊不清,难于理解,也很难使用。即使做得很好,遗留应用的模型对我们也不是那么有用,因为我们的模型很可能与它完全不同。因此,在我们的模型和遗留模型之间就须要有一个集成层,这也是使用旧应用的需求之一。
让我们的客户端系统和外面的系统交互有很多种方法。一种是通过网络连接,两个应用需要使用同一种网络通信协议,客户端需要遵从使用外部系统使用的接口。另外一个交互的方法是数据库。外部系统使用存储在数据库里的数据。客户端系统被假定访问同样的数据库。在这两个案例中,我们所处理的两个系统之间传输的都是原始数据。但是这看上去有些简单,事实是原始数据不包括任何和模型相关的信息。我们不能将数据从数据库中取出来,全部作为原始数据处理。在这些数据后面隐含着很多语义。一个关系型数据库含有和创建关系网的其他原始数据相关的原始数据。数据语义非常重要,并且需要被充分考虑。客户端应用不能访问数据库,也不能不理解被使用数据的含义就进行写入操作。我们看到外部模型的部分数据被反映在数据库里,然后进入我们的模型。
如果我们允许这样的事情发生,那么就会存在外部模型修改客户端模型的风险。我们不能忽视和外部模型的交互,但是我们也应该小心地将我们的模型和它隔离开来。我们应该在我们的客户端模型和外部模型之间建立一个防崩溃层。从我们模型的观点来看,防崩溃层是模型很自然的一部分,并不像一个外部的什么东西。它对概念和行为的操作和我们的模型类似,但是防崩溃层用外部语言和外部模型交流,而不是客户端语言。这个层在两个域和语言之间扮演双向转换器,它最大的好处在于可以使客户端模型保持纯洁和持久,不会受到外部模型的干扰。
我们怎么实现防崩溃层?一个非常好的方案是将这个层看作从客户端模型来的一个服务。使用服务是非常简单的,因为它抽象了其他系统并让我们在自己的范围内定位它。服务会处理所需要的转换,所以我们的模型保持独立。考虑到实际的实现,可以将服务看作比作一个Facade(参见设计模式》)。除了这一点, 防崩溃层最可能需要一个适配器(Adapter)。适配器可以使你将一个类的接口转换成客户端能理解的语言。在我们的这个例子中,适配器不需要一定包装类,因为它的工作是在两个系统之间做转换。
防崩溃层也许包含多个服务。每一个服务都有一个相应的Facade,对每一个Facade 我们为之增加一个适配器。我们不应该为所有的服务使用一个适配器,因为这样会使我们无法清晰地处理繁多的功能。
我们还必须再增加一些组件。适配器将外部系统的行为包装起来。我们还需要对象和数据转换,这会使用一个转换器来解决。它可以是一个非常简单的对象,有很少的功能,满足数据转换的基本需要。如果外部系统有一个复杂的接口,最好在适配器和接口之间再增加一个额外的Facade。这会简化适配器的协议,将它和其他系统分离开来。
独立方法
在谈论独立方法之前,我们需要明确的是我们不会回到集成系统。独立开发的模型是很难集成的,它们的相通之处很少,不值得这样做。
独立方法模式适合一个企业应用可由几个较小的应用组成,而且从建模的角度来看彼此之间有很少或者没有相同之处的情况。它有一套自己的需求,从用户角度看这是一个应用,但是从建模和设计的观点来看,它可以由有独立实现的独立模型来完成。我们应该现看看需求,然后了解一下它们是否可以被分割成两个或者多个不太相同的部分。如果可以这样做,那么我们就创建独立的界定上下文(Bounded Context),并独立建模。这样做的好处是有选择实现技术的自由。我们正创建的应用可能会共享一个通用的瘦GUI,作为链接和按钮的一个门户来访问每一个程序。相对于集成后端的模型,组织应用是一个较小的集成。
开放主机服务
如果外部子系统不是被一个客户端子系统使用,而是被多个服务端子系统的话,将外部子系统看作服务提供者。如果我们能为这个系统创建许多服务,那么所有的其他子系统就会访问这些服务,我们也就不需要任何转换层。
定义一个能以服务的形式访问你子系统的协议。开放它,使得所有需要和你集成的人都能获取到。然后优化和扩展这个协议,使其可以处理新的集成需求,但某团队有特殊需求时除外。对于特殊的需求,使用一个一次性的转换器增加协议,从而使得共享的协议保持简洁和精干。
精炼
精炼是从一个混合物中分离物质的过程。精炼的目的是从混合物中提取某种特殊的物质。在精炼的过程中,可能会得到某些副产品,它们也是很有价值的。
即使在我们提炼和创建很多抽象之后,一个大的领域还是会有一个大的模型。就是在重构多次之后,也依然会很大。对于这样的情况,就需要精炼了。思路是定义一个代表领域本质的核心域(CoreDomain)。精炼过程的副产品将是组合领域中其他部分的普通子域(Generic Subdomain)。
正确标识核心,以及它和其他模型之间的关系是非常重要的。找到核心域,发现一个能轻松地从支持模型和代码中区分核心域的方法。强调最有价值和特殊的概念。使核心变小。
让最好的开发人员去承担核心域的任务是重要的。开发人员经常沉溺于技术,喜欢学习最好的和最新的语言,相对于业务逻辑他们更关注基础架构。领域的业务逻辑于他们毫无兴趣可言,也看不到什么回报。但是领域的业务逻辑是业务的核心所在。这个核心的设计和实现中如果有了错误,将会导致整个项目的失败。如果核心业务逻辑不起作用,所有的技术亮点都等于垃圾。
核心域的创建不是能一朝一夕完成的。这需要一个提炼的过程,另外重构在核心越来越明显之前也是很必要的。我们需要硬性地将核心放在设计的中心位置,并划定它的界限。我们还需要重新考虑和新核心相关的其他模型的要素,也许它们也需要被重构,相关的功能也许需要被改变等。
没有特殊的知识,模型的某些部分也会增加复杂度。任何额外的事情都会使核心域难于辨认和被理解。因为广为人知的普遍原则,或者那些不是你首要关注但是扮演支持角色的细节都会影响模型,但是那些部分对系统功能完整性和模型的完整表达都依然是必要的。
标识出在你项目中不是推动因素的相关子域。找出那些子域的普通模型,并将它们放在不同的模型中。不要在这些模型中留下什么特殊的印记。
一旦它们被分离开,就将对它们的持续开发的优先级调得比核心域要低,而且注意不要把你的核心开发人员分配到这些任务中(因为它们从中获取不了多少领域知识)。另外要考虑现成的解决方案,或者那些已发布的针对普通子域的模型。
下面有几种方法可以实现普通子域:
A、购买现成的方案。
B、外包。
C、已有模型。
D、自己实现。
专访Eric Evans
领域驱动设计是我们应该专注于用户所关心领域里的重要问题的指导原则。我们的智慧应该用在理解这一领域上,和那个领域的其他专家一起将它抽象成一个概念。这样,我们就可以应用这个抽象出来的概念构造强大而灵活的软件。它是一个永远不会过时的指导原则。
注意下面几个领域建模时的陷阱:
A、事必躬亲。模型需要代码。
B、专注于具体场景。抽象思维需要落地于具体案例。
C、不要试图对任何事情都进行领域驱动设计。画一张范围表,然后决定哪些应该进行领域驱动设计,哪些不用。不要担心边界之外的事情。
D、不停地实验,期望能产生错误。模型是一个创造性的流程。