DDD 领域驱动设计学习(二)- 限界上下文

2024-01-20 11:10

本文主要是介绍DDD 领域驱动设计学习(二)- 限界上下文,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

12/9-DDD中国峰会部分文章节选

DDD不是架构,而是一种方法论(Methodology)。根据维基百科:Methodology is the systematic, theoretical analysis of the methods applied to a field of study,DDD正是针对软件领域提供的系统与理论分析方法。Eric在创造性地提出DDD时,实则是针对当时项目中聚焦在Data(主要是DB Schema)为核心的系统建模方法的批判。这种面向数据的建模方式无法应对日渐复杂的业务逻辑,也无法更好地应用当时正沸沸扬扬的OO设计思想。这是设计观念的转变,蕴含了全新的设计思想、设计原则与设计过程。
坦白说,Eric Evans的DDD奠基之作《Domain-Driven Design》并没有非常清晰的系统脉络,战略设计与战术设计也未成体系。Eric的DDD其实没有解决三个问题:
· 如何进行领域建模
· 如何识别Bounded Context
· 如何在战术层面寻找对象
DDD不是架构(设计)方法,因此不能把每个设计细节具象化。DDD是一套体系,这就决定了它必须具有开放性,在这个体系中你可以用任何一种方法来解决这些问题。但如果这些关键问题如果没有具体落地的方法,可能会让团队无可适从。这其实也是DDD在许多项目中难以推行的部分原因。

DDD的战略建模与战术建模

战略建模-Strategic Modeling:

  1. 限界上下文(Bounded Context)
  2. 上下文映射图(Context Mapping)

战术建模-Tactical Modeling:

  1. 聚合-Aggregate
  2. 实体-Entity
  3. 值对象-Value Objects
  4. 资源库-Repository
  5. 领域服务-Domain Services
  6. 领域事件-Domain Events
  7. 模块-Modules

Bound Context(BC)

先引用一段文章内容DDD分层架构的三种模式:

UL(Ubiquitous Language,通用语言)是团队共享的语言,是DDD中最具威力的特性之一。不管你在团队中的角色如何,只要你是团队的一员,你都将使用UL。由于UL的重要性,所以需要让每个概念在各自的上下文中是清晰无歧义的,于是DDD在战略设计上提出了模式BC(Bounded Context,限界上下文)。UL和BC同时构成了DDD的两大支柱,并且它们是相辅相成的,即UL都有其确定的上下文含义,而BC中的每个概念都有唯一的含义。
一个业务领域划分成若干个BC,它们之间通过Context Map进行集成。BC是一个显式的边界,领域模型便存在于这个边界之内。领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。
从广义上来讲,领域即是一个组织所做的事情以及其中所包含的一切,表示整个业务系统。由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建一个单一的、内聚的和全功能式的模型。然而,这并不是我们使用DDD的目标。正好相反,领域模型存在于BC内。

关于BC,有一个很形象的类别,细胞和细胞膜的类比:
在这里插入图片描述
细胞之所以能存在,是因为细胞膜定义了什么在细胞内,什么在细胞外,而且确认了什么物质可以通过细胞膜

BC可以类比为细胞膜,大型系统由于其复杂性,对于一个对象如果采取统一建模方式,可能会产生不可预测的效果。例如书中提到的客户发票中的收费对象故障,其实类似问题也在很多地方可以看到。
大型系统领域模型的完全统一是不可行的,也不是一种经济有效的方法。任何一个大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现 bug ,变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。当不同的团队不得不共同工作于一个模型时,我们必须小心不要踩到别人的脚。每当要时刻意识到任何针对模型的变更都有可能破坏现有的功能。当使用多个模型时,每个人在自己的模型之上可以自由地工作。我们都知道自己模型的界限,都恪守在这些边界里。我们需要确保模型的纯洁、一致和统一。
因此明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现〈代码和数据库模式等〕来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
BC明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在 CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。

CONTINUOUS INTEGRATION(持续集成-CI)

定义完一个 BC后,必须让它持续保持合理化。当很多人在同一个BC中工作时.模型很容易发生分裂。团队越大,问题就越大,但即使是3、4个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的 CONTEXT,最终又难以保持集成度和一致性。
所以一个方法是需要经常保持BC的一致,以便当模型发生分裂时,可以迅速发现并纠正问题。像领域驱动设计中的其他方法一样,CI也有两个级别的操作:(1)模型溉念的集成;(2)实现的集成。团队成员之间通过经常沟通来保证概念的集成。团队必须对不断变化的模型形成一个共同的理解,最基本的方法是对 UL多加锤炼,在讨论模型和应用程序时要坚持使用UL。同时,实际工件是通过系统性的合并/构建/测试过程来集成的,这样的过程能够尽早暴露出模型的分裂问题。
建立一个经常把所有代码和其他实现工件合并到一起的过程,并通过自动测试来快速查明模型的分裂问题。严格坚持使用 UL,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识。
最后,不要在持续集成中做一些不必要的工作。CI只有在BC中才是重要的。相邻 CONTEXT中的设计问题(包括转换)不必以同一个步调来处理。

上下文图(Context Map)

多个系统之间会发生关系,存在交互,这也必然会在各自的BC上有所表现。在项目中创建一个所有模型上下文的全局视图,可以减少混乱。上下文图(Context Map)便是表示各个系统之间关系的总体视图。
建模的过程需要识别每个模型在项目中的作用,并定义其 BC,这包括非面向对象子系统的隐含模型。为每个 BC命名,并把名称添加到 UL。描述模型之间的接触点,明确每次交流所需的转换,并突出任何共享的内容。画出现有的范围。为稍后的转换做好准备。
在Context Map中可以有如下几种形式来表征限界上下文之间的关系,简介如下(具体可阅读原书):

1. 共享内核(Shared Kernel)

当不同团队开发一些紧密相关的应用程序时,团队之间需要进行协调,通常可以将两个团队共享的子集剥离出来形成共享内核(Shared Kernel),双方进行持续集成(Continuous Integration)。共享内核(Shared Kernel)是业务领域中公共的部分,同时也是团队间容易达成且必须达成共识的领域部分。

2. 客户/供应商(Customer/Supplier)

不同系统之间存在依赖关系时,下游系统依赖上游系统,下游系统是客户,上游系统是供应商,双方协定好需求,由上游系统完成模型的构建和开发,并交付给下游系统使用,之后进行联调、测试。这种模式建立在团队之间友好合作和支持的情况下。
当两个具有上游/下游关系的团队不归同一个管理者指挥时,Customer/Supplier这样的合作模式就不会奏效。勉强应用这种模式会给下游团队带来麻烦。

3. Conformist(追随者)

当两个开发团队具有上/下游关系时,如果上游团队没有动机来满足下游团队的需求,那么下游团队将无能为力。出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。下游项目只能被搁置.直到团队最终学会利用现有条件自力更生为止。下游团队不会得到根据他们的需求而量身定做的接口。
这时候“客户/供应商”模式就不凑效了,那么下游系统只能去追随上游系统,下游系统严格遵从上游系统的模型,简化集成。
通过严格遵从上游团队的模型,可以消除在 BC之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择 Conformist模式可以极大地简化集成。此外,这样还可以与供应商团队共享一种 UL。供应商处于驾驶者的位置上,因此最好使他们能够容易沟通。

4. 防腐层(Anticorruption Layer)

前面介绍了在两个BC之间集成时可以进行的各种合作,从高度合作的 Shared Kernel模式或 Customer/Supplier Team到单方面的Conformist模式。如果是一种更悲观的关系,假设一个团队既不可能与另一个团队合作也无法利用他们的设计时,该如何应对。
这时候我们需要使用防腐层(Anticorruption Layer)模式将上游系统的影响降低。

5. 公开主机服务(Open Host Service)

当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。如果一个子系统有某种内聚性,那么或许可以把它描述为一组 Service,这组 Service满足了其他子系统的公共需求。
公开主机服务(Open Host Service)能够允许系统将一组Service公开出去公其他系统访问。定义一个协议,把你的子系统作为一组 Service供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。

6. 各行其道(Separate Way)

当两个系统之间的关系并非必不可少时,两者完全可以彼此独立,各自独立建模,独立发展,互不影响。

Context Map例子

在这里插入图片描述

U表示上游(Upstream)的被依赖方,D表示下游(Downstream)的依赖方。防腐层(ACL)放在下游,将上游的消息转化为下游的领域模型。

模式图谱模式图谱

作者:njluz
链接:https://www.jianshu.com/p/3af5f7c0ec7c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于DDD 领域驱动设计学习(二)- 限界上下文的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/625818

相关文章

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

如何在Ubuntu上安装NVIDIA显卡驱动? Ubuntu安装英伟达显卡驱动教程

《如何在Ubuntu上安装NVIDIA显卡驱动?Ubuntu安装英伟达显卡驱动教程》Windows系统不同,Linux系统通常不会自动安装专有显卡驱动,今天我们就来看看Ubuntu系统安装英伟达显卡... 对于使用NVIDIA显卡的Ubuntu用户来说,正确安装显卡驱动是获得最佳图形性能的关键。与Windo

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决