在微服务架构落地实践的过程中,工程师往往会遇到微服务的粒度与边界划分等实践问题,DDD(Domain Driven Design,领域驱动设计)是解答这些问题的关键技术之一,它是一套完整且系统的设计方法。

所以在本课时,我们将会首先介绍微服务划分的困境,然后再讲解运用 DDD 应对软件复杂度之法,最后介绍 DDD 战略模式中的相关概念。

微服务就是“小”服务吗?

早在微服务架构出现之前,软件系统基本都采用单体架构来构建,但是单体架构模式在解决大型复杂软件的开发和效率问题上有着天然的不足。工程师们在拆分单体架构模式的遗留系统时,会粗暴地将大单体应用拆分为多个微服务,比如说一个电商系统根据应用层的接口划分,不可避免地会出现业务交叉的情况,在耦合的情况下,开发者就直接合并业务接口或者细分到原子,这些错误的方式使得开发者陷入业务的“泥潭”中。这是因为路径依赖法则的存在,在这样的情况下由单体架构演进到微服务架构就变得不是那么简单了。那什么是路径依赖法则呢?我们先看下它的定义:

路径依赖法则是指人类社会中的技术演进或制度变迁均有类似于物理学中的惯性,即一旦进入某一路径(无论是好还是坏)就可能对这种路径产生依赖。

对于一直使用单体架构开发的工程师来说,刚开始使用微服务架构拆分、重构旧业务系统或者开发新的业务系统时,很多人都会陷入“将原来的单体应用做小”的思维定式,这会导致开发出来的微服务可能粒度确实够小,但是中间件层却是各种耦合,领域边界更加不清晰。所以微服务不是“小”服务,上述情况下的“微服务”将分布式的基础设施和环境的困难凸显出来,却无法体现微服务架构的优势,也无法提高开发团队应对业务变化的响应速度。

DDD 应对软件复杂度之法

当前的产品研发面临迭代速度快、业务场景复杂以及软件开发效率低下等问题,这些也是扼制企业高速发展的症结,DDD 就是其中一种的解决思路,对于应对这些问题有其思想做指导,能够更好地构建稳定的产品内核。

DDD(Domain Driven Design,领域驱动设计)的概念出现于 2003 年,与敏捷相比,DDD 在提出之后的很多年都没有称得上“流行”,甚至说从未真正流行过。一部分是因为 DDD 涉及了一些新的名词和概念(比如聚合、限界上下文和领域等),在缺乏具体实践案例的场景下,较难理解这些抽象概念,这导致了学习和应用 DDD 的初期阶段就变得不太顺畅,开发人员可能并不能领会到其中的设计思想及其带来的价值。但是在小范围群体内,逐渐有一批工程师开始能够掌控这种建模方法,并使用 DDD 来设计出具有较高业务复杂性的软件应用。

2013 年后,分布式的基础设施逐渐成熟。Martin Fowler 于 2014 年发表了系统阐述微服务的文章,微服务架构开始兴起。软件工程师们发现将单体应用采用微服务架构进行划分需要大量的实践经验和理论基础作指导,否则不能完全体现微服务架构所带来的优势。不过早期熟悉 DDD 思想的开发者发现,DDD 可以有效地根据业务对复杂软件系统进行拆解,微服务架构与 DDD 相得益彰。按照软件工程的思想,我们在创建微服务时,需要满足高内聚、低耦合的要求。而根据 DDD 的思想,可以将限界上下文与微服务进程对应起来。DDD 中限界上下文的概念很契合匹配微服务要求,这两者都强调从业务角度进行划分,以应对日益复杂的软件系统。由此,DDD 迎来了它的高速发展和推广时期。

作为开发人员,你应该对于分层架构已经比较熟悉了,因为在平常的项目中应该没少使用它。《领域驱动设计》一书中, Eric Evans 提出了经典的四层分层架构,如下图所示:

DDD 领域分层

其各个层面的作用如下表所示。

层级名称

描述

用户界面(表现层)

负责给用户展示信息,并解释用户命令。

应用层

负责协调应用程序的活动。不包括任何业务逻辑,不保存业务对象的状态,但能保存应用程序任务过程的状态。

领域层

负责业务领域的信息和状态的保存和维护。业务对象的持久化和它们的状态可能会委托给基础设施层。

基础设施层

负责支持其他层次,提供基础的消息传递、数据持久化等功能。它提供层之间的信息传递,实现业务对象的持久化,包含对用户界面层的支持性库等。

这样讲述可能有点抽象,下面我们以购物车下订单功能为例来解释这各个层面的作用:用户界面层提供下单的接口;应用层负责逻辑的整合,如购物车清空、检查库存等;领域层将购物车相关的业务逻辑封装到一个 ShoppingCar 对象中,调用 shoppingCar.order()下订单,业务服务的重心从生成订单表中的记录转移到购物车对象本身;底层数据库中如何生成这条记录并不属于我们的核心业务逻辑,这对应 DDD 中的基础设施层,由 Repository 或者 Dao 等数据交互对象负责去持久化我们对领域模型下达的指令所产生的数据库变化。

DDD 不是语言,不是框架,不是架构,而是一种思想,一种方法论,它可以分离业务复杂度和技术复杂度;DDD 也并不是一个新的事物,它是面向对象的提升,最终目标还是高内聚、低耦合。

DDD 是不是银弹?

在介绍完 DDD 的应用领域之后,我们来讨论下 DDD 是不是银弹。很显然,市面上能看到的书籍和搜索引擎搜出的结果都会告诉你答案:DDD 不是银弹。DDD 并不能解决软件系统的所有“疑难杂症”。

由于业务场景的不确定性,设计的方案并没有标准答案。从 DDD 战术角度来看,若要做到优良,设计的经验必不可少,这需要持续的项目实战打磨积累;但如果在开始,能有一些更为具体的方法作为指引,就能达到事半功倍的效果,这就是 DDD 的战略模式。我认为,DDD 的战略模式在架构设计层面功能一直都很强大。

使用 DDD 指导开发复杂软件系统,从本质上并不会降低应用系统本身的复杂度。DDD 的战略模式对于理解任何领域都很有用,而一些战术模式则可能与你的业务上下文不太相关,或者说不可以直接拿来用。但 DDD 核心价值在于,它能帮你从战略设计到战术设计的过程进行规范,在设计系统时就能思路清晰,从而使得设计过程更加规范。

下面我们就来具体介绍 DDD 的战略模式,而具体的 DDD 战术设计则会在后面模块的实战案例中体现。

DDD 战略模式

DDD 概念理解起来有点抽象,而且不容易应用于实践中。就像设计模式一样,感觉很有用,但是在真正实践中做到拿来就用却很困难。虽然如此,正确并且深入了解 DDD 的概念仍然是十分必要的。DDD 中根据问题域,将问题划分为领域/子域、通用语言、限界上下文和架构风格等概念。

1. 领域和子域

领域(Domain),即一个业务范围以及这个业务范围内的软件活动。领域层是具体的业务领域层,是发生业务变化最为频繁的地方,是业务系统最核心的一层,是 DDD 关注的焦点和难点。一个组织有一个大的业务范围,然后划分为组织部门,也会划分为子业务,这就是子域的概念。相应地,对于软件设计,就是把一个大的系统划分为多个模块,即多个子域。

业务中所有内容构成了这个业务系统的领域,也就是说它是唯一的。我们开发任何业务系统,首先需要明确业务系统的领域是什么。比如一个在线教育平台,产品包含的业务有学生管理、在线教学、课程等内容,我们可以将这些业务抽象出领域模型,然后根据领域模型的设计进行代码开发的相关工作。

从如下的领域示意图可以看出,领域中存在核心域,重要而又复杂。通常来说,一个业务系统有且只有一个核心域,并且核心域的提炼往往影响着整个系统的设计成败。在核心域明确之后,开发者和领域专家将会围绕这个核心域展开工作。

领域示意图(一)

除了核心域,领域中还有子域(可以理解为重要性没有核心域那么大的其他域)。子域进一步可以分为支撑子域(支撑核心域的子域)、通用子域(业务领域中,某些可以被公用的子域)。比如说,在商品秒杀项目中,秒杀是核心域,活动管理域用来支撑秒杀核心域,只有创建秒杀活动,并且查询秒杀活动详情后才可以进行秒杀,所以它是支撑子域,而通用的用户鉴权领域则是通用子域。

2. 限界上下文和通用语言

关于限界上下文,我们首先看如下的一个小故事:

大街上一个小孩手拿一捧彩色的“棉花糖”,红红的小脸蛋,看上去非常有趣。一旁的孩子奶奶问道:这棉花糖“盯不盯人”?棉花糖“盯不盯人”?难道棉花糖会盯人吗?奶奶补充说:“棉花糖吃了不会让孩子胃胀吧”?

上面这个 “盯不盯人”的小故事,强调理解也需要上下文。当我们在理解系统的领域需求时,同样需要借助这样的上下文,而限界上下文的含义就是用一个清晰可见的边界将这个上下文勾勒出来,如此就能在自己的边界内维持领域模型的一致性与完整性。

比如前面我们提到的商品秒杀系统,其领域示意图如下所示,核心域与子域的界限使用虚线分隔,秒杀为核心域,秒杀活动管理为支撑子域,认证鉴权为通用子域。可以看到限界上下文和子域是对应的关系,比如通用用户认证鉴权子域对应认证鉴权上下文。不过二者也存在不同,限界上下文中还存在空白的地方,这是因为通用子域不仅仅只有认证鉴权上下文,还可能包含其他上下文,如消息与通知上下文等。支撑子域也是类似。

领域示意图(二)

DDD 是围绕着“领域”来开展软件设计的。在明确了系统的问题域和业务期望后,可以梳理出主要的业务流程,这些业务流程体现了各种参与者在这个过程中通过业务活动共同协作,最终完成具有业务价值的领域功能。因此,一门通用的语言对于开发人员、产品经理、测试人员之间达成共识显得很重要了。通用语言是一个各种概念的集合,将一个限界上下文中的名词、动词和形容词全部集中在一起,且具有简洁、清晰的特性,这套通用语言与限界上下文对应,开发、产品和测试都基于通用语言进行沟通。

理想情况下,限界上下文与微服务可以一一对应,但在实际项目中,还会有一些调整,比如根据业务的相关度和变化频率,有时候就需要将多个限界上下文进行合并。

小结

在微服务刚兴起时,很多企业或者架构师对微服务架构划分的粒度都没有统一且明确的定义,往往采用诸如代码行数、职责的划分、比萨原则和组织结构等规则进行判断,无一例外,这些规则都无法准确地对粒度进行判定。DDD 为微服务的边界拆分提供了方法论,可以解决微服务的粒度问题。

本课时我们主要讲解了运用 DDD 进行领域场景分析的战略模式。DDD 不是银弹,实证软件工程强调了经验的重要性,然而 DDD对于复杂软件系统的整体架构的搭建,有着重要的指导作用。你在进行 DDD 战术设计时,需要首先掌握本课时所讲解的 DDD 战略模式。在接下来的具体案例实践中,我们将会结合案例讲解 DDD 战术设计的过程。

学习完本课时,你理解 DDD 的战略模式了吗?欢迎你在留言区分享你的想法。

-– ### 精选评论 ##### **繁: > DDD的战略模式,主要为两个方面:限界上下文和分层架构,强调以领域为核心。 ##### **富: > 感觉这么多概念只是点到为止 不够过瘾啊!😁 ##### **阳: > 觉得DDD应该是在设计的时候确定业务的最主要内容,然后其他内容都是基于这个内容做延伸与支撑。