实现领域驱动设计
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

总览

从广义上讲,领域(Domain)即是一个组织所做的事情以及其中所包含的一切。商业机构通常会确定一个市场,然后在这个市场中销售产品和服务。每个组织都有它自己的业务范围和做事方式。这个业务范围以及在其中所进行的活动便是领域。当你为某个组织开发软件时,你面对的便是这个组织的领域。这个领域对于你来说应该是明晰的,因为你在这个领域中工作。

有一点需要注意的是,“领域”这个词可能承载了太多含义。领域既可以表示整个业务系统,也可以表示其中的某个核心域或者支撑子域。在本书中,我将尽可能地区分这些概念。当谈及到业务系统中的某个方面时,我会使用诸如“核心域”或者“子域”以示区别。

由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建一个单一的、内聚的、全功能式的模型。然而,这并不是我们使用DDD的目标。正好相反,在DDD中,一个领域被分为若干子域,领域模型在限界上下文中完成开发。事实上,在开发一个领域模型时,我们关注的通常只是这个业务系统的某个方面。试图创建一个全功能的领域模型是非常困难的,并且很容易导致失败。就像本章中所讲到的一样,对领域的拆分将有助于我们成功。

那么,既然领域模型不能包含整个业务系统,我们应该如何来划分领域模型呢?

几乎所有软件的领域都包含多个子域,这和软件系统本身的复杂性没有太大关系。有时,一个业务系统的成功取决于它所提供的多种功能,而将这些功能分开对待是有好处的。

工作中的子域和限界上下文

对于如何使用子域,让我们先来看一个非常简单的例子——一个零售商在线销售产品的例子。要在这个领域中开展业务,该零售商必须向买家展示不同类别的产品,允许买家下单和付款,还需要安排物流。在这个领域中,零售商的领域可以分为4个主要的子域:产品目录(Product Catalog)、订单(Order)、发票(Invoicing)和物流(Shipping)。图2.1的上半部分表示了这样一个电子商务系统。

这看来是非常简单的,但是,如果我们再向其中加入一个额外的细节,以上这个例子将变得复杂起来。思考一下,如果我们向以上的电子商务系统中再加入一个库存(Inventory)系统,如图2.1所示,情况会变得如何?接下来,让我们来看看图2.1所展示的物理子系统和逻辑子域。

该零售商的领域中只包含了三个物理系统,其中有两个是内部系统。这两个内部系统表示两个限界上下文。不幸的是,由于现在多数软件系统并没有采用DDD,这导致了少数的几个子系统承担了太多的业务功能。

img

图2.1 一个含有子域和限界上下文的领域

在上面的电子商务限界上下文中,我们的确可以找出多个隐式的领域模型,即便它们并没有被很好地分离出来。事实上,这些领域模型被融合成了一个软件模型,这是不幸的。对于该零售商来说,与其自己开发,还不如从第三方购买这么一个限界上下文,因为这样所带来的问题可能会少一些。然而,不管是谁来维护这个系统,它都将承受这个大而全的电子商务模型所带来的负面影响。随着各个逻辑模型中不断加入新的功能,它们之间的复杂关系对于每一个模型都将是阻碍,特别是需要引入另外一个逻辑模型的时候。这些问题的原因通常都是由于软件的关注点没有得到清晰的划分所致。

更不幸的是,很多软件开发者都认为将所有东西都放在一个系统里面是一件好事。他们会想:“我对电子商务系统了如指掌,我相信这个系统可以满足任何人的需求。”这是具有欺骗性的,因为不管你向系统中添加多少功能,你都无法满足每一个潜在客户的需求。此外,如果不通过子域对软件模型进行划分,事情将变得更加烦琐,因为系统中的各个部分都是紧密联系在一起的。

然而,通过使用DDD战略设计工具,我们可以按照实际功能将这些交织的模型划分成逻辑上相互分离的子域,从而在一定程度上减少系统的复杂性。逻辑子域的边界在图2.1中以虚线表示。这里,我们将第三方的模型也做了清晰地划分,但这不是我们的重点,我们的重点在于说明应该存在什么样的分离模型。在不同的逻辑子域之间或者不同的物理限界上下文之间均画有连线,这表示它们之间存在集成关系。

现在,让我们将视线从技术复杂性转向这个零售商的业务复杂性。该零售商的资金和仓库容量均有限。对于那些销量不佳的产品,该零售商不敢过量投入。显然,如果有产品没有按照计划销售出去,那么该零售商的流动资金将出现问题。因此,它只能用有限的房间来存储那些销量好的产品。

这还没完,还有另外一个问题。如果有些产品销量好于预期,那么该零售商便没有足够的库存,此时顾客将不得不到别处购买商品。当然,有些产品生产商会自己负责产品的物流,但是这样对于零售商来说成本更大,并且会带来一些不良后果。另一种节约成本的做法是,对于本地售出产品采用自留库存,而对于偏远地区则采用生产商直接发货的方式。这样,该零售商便不至于在库存清空时捉襟见肘了。事实上,导致库存清空的原因并不是产品销售得异常好,而是该零售商没有找到一种最优的库存管理方式。如果经常发生产品不能及时送达客户的情况,那么该零售商将损失很大一部分竞争优势。

库存问题不仅是小型零售商的问题,大型零售商同样面临这样的问题。各个零售商家都根据准确的需求来囤积产品,从而减少成本,优化销售业务。但是,在库存问题面前,小型零售商比大型零售商更加脆弱。

一种好的做法是,根据过去的销售趋势来制定未来的库存计划。零售商可以采用一个预测引擎,根据库存和销售历史来分析产品的需求量,从而达到优化库存系统的目的。

对于小型零售商来说,增加预测引擎可能意味着开发一个新的核心域,这并不是一个容易解决的问题,但是可以大大增加竞争优势。事实上,图2.1中的第三个限界上下文便是一个外部预测系统。订单子域和库存限界上下文向预测系统提供历史销售数据。此外,我们还需要产品目录子域来提供全局的产品条形码,这将有助于预测系统在全球范围之内对产品的销售情况进行比较。这样一来,预测系统便可以精确地计算出产品的需求量,并指导零售商制定正确的库存计划。

如果这个新的解决方案是一个核心域,那么开发团队将从周围的逻辑子域以及集成体系中受益。因此,在该核心域的项目启动时,图2.1中已有的集成体系对于掌握项目情况来说将起到关键作用。

子域并不是一定要做得很大,并且包含很多功能。有时,子域可以简单到只包含一套算法,这套算法可能对于业务系统来说非常重要,但是并不包含在核心域之中。在正确实施DDD的情况下,这种简单的子域可以以模块(Module,9)的形式从核心域中分离出来,而不需要包含在笨重的子系统组件中。

在实施DDD的时候,我们致力于将限界上下文中领域模型所用到的每一个术语都进行限界划分。这种限界主要是语言层面上的上下文边界,也是实现DDD的关键。

牛仔的逻辑

LB:“有护栏相隔时,我和邻居相处得很好,护栏坏了之后,情况就变了。”

AJ:“对的,你需要将你的护栏造成和马一样高。”

需要注意的是,一个限界上下文并不一定只包含在一个子域中,但这是可能的。在图2.1中,只有库存限界上下文包含在了一个子域中。显然,这表明这个电子商务系统在开发的时候并没有正确地采用DDD。在这个系统中,我们识别出了4个子域,然而还可能有更多的子域。另一方面,库存系统看起来的确符合“一个子域对应一个限界上下文”的标准。库存系统这种清晰的模型有可能是采用了DDD的结果,也有可能是种偶然,对此我们需要深入地研究。但无论如何,我们都可以使用这个库存系统来开发新的核心域。

在图2.1中,哪种类型的限界上下文在语言表达层面上设计得更好呢?换句话说,哪种限界上下文拥有非歧义的领域特定术语?在上面的电子商务系统中,当我们谈到其中有4个子域时,我们可以非常确定地认为有些术语在这些子域中是存在冲突的。比如,“顾客”这个术语可能有多种含义。在浏览产品目录的时候,“顾客”表示一种意思;而在下单的时候,“顾客”又表示另一种意思。原因在于:当浏览产品目录时,“顾客”被放在了先前购买情况、忠诚度、可买产品、折扣和物流方式这样的上下文中。而在下单时,“顾客”的上下文包括名字、产品寄送地址、订单总价和一些付款术语。单单从这个例子我们便可以看出,在这个电子商务系统中,“顾客”并没有一个清晰的含义。我们甚至还可以找到很多像“顾客”这样拥有多重含义的术语。在一个好的限界上下文中,每一个术语应该仅表示一种领域概念。

然而,我们同样不能保证库存系统的模型就是完全清晰的,并且使用了完全非歧义的领域语言。即使是在关注点分离明显的限界上下文中,也会存在和上面的“顾客”相似的情况,因为库存件可能用在不同的环境下。比如,有的库存件已经被订购了,有的正在运送途中,有的正保存在仓库中,而有的正被移出仓库。已经被订购但还无法销售的产品称为延期订单件;保存在仓库中的产品称为积压件;刚被购买的产品称为即将发送件;而被损坏的库存产品称为无用件。

在图2.1中,我们看不出以上这些库存概念。在DDD中,我们不能靠猜测,而应该对每个概念都给出明确的定义,并将这些明确的定义用在交流和建模中。领域专家对这些概念的解释有助于在不同的限界上下文中分离这些概念。

从表面看来,我们可以得出结论:库存系统比电子商务系统具有更高的DDD健康指数,原因可能是库存系统的开发团队并没有使用一个库存件来表示所有的概念。虽然目前我们还不明确这一点,但是我们可以肯定的是:库存系统模型比电子商务系统模型更容易集成。

图2.1进一步表明,一个企业的限界上下文并不是孤立存在的。即便有第三方的电子商务系统可以提供一个全方位式的模型,它也不能完全满足零售商的需求。不同子域之间的实线表示集成关系,这也表明不同的模型是需要协同工作的。集成的方式有很多种,我们将在上下文映射图(3)中学到不同的集成方案。

以上,我们在一个高层面上对一个简单的业务领域进行了总结。我们简要地学习了一个核心域,并且了解了核心域对于DDD的重要性。接下来,我们需要深入学习核心域。

将关注点放在核心域上

了解了子域和限界上下文,现在我们来看看关于领域的另一个抽象视图,如图2.2所示。该抽象视图可以表示任何一个领域,甚至有可能是你正在工作的领域。和图2.1相比,我去除了那些具体的名字,你可以根据自己的项目情况进行填补。我们会持续改进并且扩大业务目标,这将反映在不断变化的子域和子域模型中。图2.2仅仅表示某个时刻,从某个角度看的业务领域,这样的领域可能并不会驻留多久。

img

图2.2 一个抽象的业务领域,其中包含了子域和限界上下文

白板时间

• 在一栏中列出你日常工作中的子域,然后在另一栏中列出限界上下文。子域和限界上下文有相交的地方吗?如果有,这并不是什么坏事,因为这正是企业级软件的本来面目。

• 以图2.2为模板,根据你自己的软件项目填入相应的名字,包括子域、限界上下文和集成关系。

这困难吗?有可能,因为图2.2中的模板可能并没有反映出你工作领域的边界。

• 再来一次,画出能表示你的领域、子域和限界上下文的框图。你可以参考图2.2,但是所画框图应该表示你自己的领域。

当然,你不用了解所有的子域和限界上下文,特别是当你的领域非常复杂的时候。但是,你可以将那些你每天都会接触到的子域和限界上下文识别出来。不管怎样,先试试再说,不要害怕犯错误。你会在下一章的上下文映射图中学到好的实践方法,如果你想现在就跳到下一章去也是可以的。总之,不要担心现在还不完美,我们首先需要了解基本概念。

现在,看看图2.2上半部分的领域边界,你会看到一个叫核心域的子域。对于核心域,我们在前面的章节中已经讲到了,它是整个业务领域的一部分,也是业务成功的主要促成因素。从战略层面上讲,企业应该在核心域上胜人一筹。我们应该给予核心域最高的优先级、最资深的领域专家和最优秀的开发团队。在实施DDD的过程中,你将主要关注于核心域。

图2.2中还展示了另外两种子域:支撑子域和通用子域。有时,我们会创建或者购买某个限界上下文来支撑我们的业务。如果这样的限界上下文对应着业务的某些重要方面,但却不是核心,那么它便是一个支撑子域。创建支撑子域的原因在于它们专注于业务的某个方面,否则,如果一个子域被用于整个业务系统,那么这个子域便是通用子域。我们并不能说支撑子域和通用子域是不重要的,它们是重要的,只是我们对它们的要求并不像核心域那么高。

白板时间

• 为了巩固你对核心域概念的掌握,你可以温习一遍先前所绘的框图,看看自己能否识别出核心域。

• 接下来,看看自己能否识别出支撑子域和通用子域。

记住:向领域专家提问!

即便在开始时你会犯下错误,但这种练习可以帮助你仔细地思考哪些是最重要的,哪些是起辅助作用的,哪些是无关紧要的。

对于你所绘框图中的子域和限界上下文,你需要和领域专家进行讨论。

你不但可以从领域专家那里学到很多,同时你还能学到聆听的技巧,这也是正确实施DDD的标志。

到此,我们总览式地学习了DDD战略设计的基础。