前言
软件开发是困难的,但这是软件本质的一部分。软件开发需要理解事物的本质,通过业务驱动,目的是达成各方的一致认知。领域驱动设计能够便于我们建立起正确的概念模型。
领域驱动设计同时提供了战略和战术的建模工具,来帮助使用者设计和实现高价值的软件。借助领域驱动设计,使用者可以在当今竞争激烈的商业环境中持续的交付最有效的软件设计与实现。
领域驱动设计从战略设计开始,优先完成宏观层面设计。通过定义限界上下文来分离领域模型,在限界上下文中发展一套领域模型的通用语言,使用子域来处理系统中的复杂性,利用上下文映射来集成多个限界上下文。
在完成战略设计的基础上,战术实施将会围绕各个子域展开,在子域中将若干实体和值对象聚合在一起,使用领域事件将实体内部发生的事情分享给需要知道该事情的所有系统。
接下来将会围绕领域驱动设计的战略设计和战术实施展开介绍。
战略设计
领域驱动设计的战略设计工具可以帮助使用者做出最有竞争力的软件设计选择和业务整合决策,使用者和其组织将从这些明确反映其核心业务竞争力的软件模型中获得最大的收益。
定义限界上下文
限界上下文是语义和语境上的边界。模型是在限界上下文中实现的,开发者将会为每个限界上下文开发出不同的软件。限界上下文如下图所示:

一个开发团队应该应该在一个限界上下文中工作。每个限界上下文应该拥有一个独立的源代码仓库,一个团队可能工作在多个限界上下文中,但是多个团队不应该在同一个限界上下文中共事。
应该把不同限界上下文的源代码和数据库表隔离开,将同一个限界上下文中源代码和单元测试代码放在一起,该限界上下文的开发团队控制着源代码和数据库并定义了接口,必须通过这些接口才能调用该限界上下文。
限界上下文的定义需要来自于好的需求分析,需要各个角色参与,通过协同来统一认知,这个过程需要能够结构化需求的探索过程,以目标驱动为出发点,分析业务的操作流程和规则。
使用通用语言
开发团队在限界上下文中发展了一种语言用于表达其边界内的软件模型,该语言为所在限界上下文中开发软件模型的每个团队成员所使用,该语言称为通用语言。每个限界上下文中,都会产生出自己的通用语言,如下图所示:

当开发团队中有人使用通用语言进行交流时,其他人都明白所表达的准确含义和约束条件。通用语言和开发软件模型中使用其他语言一样,在团队中无处不在。
拆分定义子域
领域驱动设计项目中会定义很多限界上下文,而这些限界上下文中一定会存在核心域,也会存在许多不同的子域。一般来说限界上下文和子域之间是一一对应的,一个清晰的限界上下文,也是一个清晰的子域。
子域是整个业务领域的一部分,可以认为子域代表一个单一和有逻辑的领域模型。子域可以用来逻辑拆分整个业务领域,这样才能有效的分治和理解复杂的业务场景,以电商交易业务为例,该拆分过程如下图所示:

子域主要有三种类型:
(1)核心域,它是一个唯一且定义明确的领域模型,要对它进行战略投资,并且在一个明确的限界上下文中投入大量资源去精心打磨通用语言;
(2)支撑子域,这类场景提倡“定制开发”,因为找不到现成的解决方案,重要程度弱于核心域;
(3)通用子域,可以采购现成的,考虑集成到整个业务领域中。
在微服务架构中,每个子域将会被定义为至少一个微服务应用,一个子域是一个内聚的问题空间,可以用一句话来核心陈述领域的概念。同时一个子域是能够自治的,在微服务的划分中,可以根据子域中的功能、使用频度和使用者等不同维度再次拆分,依靠异步事件等方式来确保子域的事务完整性。
运用上下文映射
限界上下文之间会进行交互和集成,这种关系在领域驱动设计中称为上下文映射,连接两个限界上下文之间的线段就代表了上下文映射,如下图所示:

两个限界上下文中存在不同的通用语言,上下文映射也就是这条线段,代表着两种通用语言之间的转换过程,而上下文映射分为以下多种类型:
(1)合作关系,该类型的上下文映射存在于两个开发团队之间,两个团队依赖一套目标联合起来;
(2)共享内核,该类型的上下文映射共享一个小规模却通用的模型;
(3)供应和消费,该类型的上下文映射描述两个团队之间,其中一个供应服务,另一个消费,支配该上下文映射的一般是供应的一方;
(4)跟随者,该类型的上下文映射描述上下游团队,上游团队没有任何动机满足下游团队,下游顺应上游的模型;
(5)防腐层,该类型的上下文映射描述下游团队在其与上游之间创建了一个转换层,隔离了两个限界上下文模型,完成二者之间的翻译;
(6)开放主机服务,该类型的上下文映射会定义一套协议或接口,让限界上下文可以被当做一组服务访问。
战术实施
领域驱动设计的战术实施工具可以帮助使用者设计出实用的软件,能够对业务独一无二的运作方式进行精准的建模。使用者可以有更多的选择,把解决方案部署到各种不同的基础设施中,不管这些设施是企业内部还是在公有云上。
使用聚合
聚合由一个或者多个实体组成,其中一个实体被称为聚合根。聚合的组成还包括了值对象,值对象不是事物,而是被用来描述、量化或者测量的一个实体,是一个具有修饰性且不变的模型。下图中展示了两个聚合:

每个聚合的根实体,也就是聚合根,控制着所有聚合在该根上的其他元素,聚合根实体的名称就是聚合概念上的名称。每个聚合都会形成保证事务一致性的边界,一般一次事务中修改一个聚合实例并提交,并且修改入口是聚合根。
聚合设计的基本规则包括:
(1)在聚合边界内保护业务规则不变性;
该规则表示聚合的组成部分应该由业务最终决定,而且要以那些在一次事务提交中必须保持一致的内容为基础。
(2)聚合要设计的小巧;
该规则强调每个聚合的内存占用空间和事务包含范围应该相对较小,概念上要考虑单一职责原则。
(3)只能通过外键引用其他聚合;
该规则进一步帮助聚合设计的足够小巧,提升数据访问效率,并且强化不要在同一次事务中修改其他聚合实例的数据。
(4)使用最终一致性更新其他聚合。
该规则推荐使用领域事件来完成其他聚合的更新。
对于聚合设计的一个检验规则就是看聚合边界能否看守的好,可以用生命周期的视角来观察聚合根,如果聚合根的生命周期变化和相关实体不一致,那么这个聚合设计就不一定正确。
运用领域事件
领域事件是一条记录,记录着在限界上下文中发生的对业务产生重要影响的事情。领域事件往往会在战术实施过程中被概念化并演变成核心域的组成部分。领域事件可以在限界上下文中发布或消费,它将重要事件告知感兴趣的监听器,而监听器可以位于不同的限界上下文中,这个过程一般需要消息中间件支持,如下图所示:

领域事件类型名称应该是对已发生事情的陈述,一般会用动词的过去式来表示,例如:产品创建,会使用ProductCreated表示一个产品被创建后发出的领域事件。领域事件和同步调用的区别在于,同步调用可以在某些情况下被拒绝,比如:某些资源已经耗尽等,但领域事件代表着事实的发生,必须无条件接受和处理。
网友评论