当前位置: 代码迷 >> 驱动开发 >> IDDD 实现领域驱动设计-懂得限界上下文
  详细解决方案

IDDD 实现领域驱动设计-懂得限界上下文

热度:376   发布时间:2016-04-28 10:01:31.0
IDDD 实现领域驱动设计-理解限界上下文

上一篇:《IDDD 实现领域驱动设计-理解领域和子域

《实现领域驱动设计》前两章内容,基本上读完了,和《领域驱动设计》不同的是,它把很多的概念都放在前面进行讲述了,比如领域精炼、界限上下文等等,在《领域驱动设计》中,是很靠后的内容,不过这样也好,可以让你从一个大局的视角去看待问题,由广到细的思路学习,我觉得也蛮好的。另外,随着一点一点的学习,你会发现,领域驱动设计越来越有意思了,有很多“新鲜”的东西等待发现。


一张很重要的图(无意间搜到),引自:《Implementing DDD Reading - Strategic Design

战略建模(Strategic Modeling)和战术建模(Tactical Modeling)

战略建模和战术建模,其实是《实现领域驱动设计》最前面的内容,位于《如何使用本书》部分,当时看的时候并没有很注意,但在前两章的内容中,发现有很多这样的字眼:“团队有人花额外的时间去了解战术模式、团队采用的是战略模式的建模方式。。。”,这就不得不让你回过头看下,什么是战略建模和战术建模?其实,关于这两点,作者并没有很准确的进行定义,只是分别描述了这两点内容的关键字,我们来总结一下:

  • 战略建模:界限上下文(Bounded Context)、上下文映射图(Context Mapping)。
  • 战术建模:聚合(Aggregate)、实体(Entity)、值对象(Value Objects)、资源库(Repository)、领域服务(Domain Services)、领域事件(Domain Events)、模块(Modules)。

像聚合、实体、值对象等,都可以称之为战术建模的工具,战略建模和战术建模的区别,你可以从字面上进行理解,战略的意思,就是从大局出发,是一种运筹帷幄的感觉,那为什么和界限上下文有关呢?在《理解领域和子域》中,有一张很重要的图,领域是业务系统的全部,其中包含核心域、子域和通用子域,相对应的就是限界上下文,你可以把某一块的领域和限界上下文进行映射,他们都是通用语言的一种表述,在项目之初,领域专家和开发人员的工作就是探讨限界上下文的划定,这个非常重要,如果限界上下文的划定有问题,那么将来战术建模的进行将“一塌糊涂”,就像作者一个例子一样,团队成员将用户和权限限界上下文划到具体的子域中实现,最后导致了一系列的问题,后来,团队发现问题后,将用户和权限限界上下文重新定义为身份和安全限界上下文,并划分到通用子域中,最后的效果显而易见,避免了很多问题的发生,也增加了业务系统的灵活性。

如果你注意的话,会发现上面说的只是“纸面”上的探讨,也就是说都没有进行实施,所以才称之为战略建模,而战术建模可以理解为战略建模的实现,前提是界限上下文都已经划定好,并确定无误。

问题空间(Problem Space)和解决方案空间(Solution Space)

和战略建模、战术建模一样,又是一个概念性的问题,在问题空间中,我们思考的是业务所面临的问题和挑战,而在解决方案空间中,我们思考的是如何实现软件以解决这些业务挑战。

具体什么意思呢?其实,问题空间和战略建模的概念有些类似,但只是思考的方式类似,他们是两个不同的概念,在上面图中,问题空间包括两部分:业务所面临的挑战、核心域+其他子域的组合,注意其中并不包含限定上下文的划分,领域专家和开发人员在探讨领域的设计中,首先,就是对问题空间的探讨,用来确定核心域和其他子域,并列出业务系统中可能会存在的一些问题。

在上面图中,解决方案空间包含的内容很多,它是什么的解决方案?其实就是针对问题空间的解决方案,当问题空间被确定下来后,我们就会对核心域以及其他子域进行探讨和实施,然后在其中划分出很多的限界上下文,并用软件的方式进行实现。

如果这样进行思考,你会发现,问题空间和解决方案空间对应于战略建模和战术建模,他们之间是有一些相似处,比如一个是探讨、战略,一个是实施、实现,但还是有些不同,比如界限上下文是战略建模中的概念,对应与问题空间和解决方案空间,界限上下文却是解决方案空间中的的概念,可以说问题空间和解决方案空间涵盖的东西很多,像战术建模就可以看作是解决方案空间实施的一种手段。

问题空间和解决方案空间,你可以不把它看作是领域驱动设计中的概念,因为在原著《领域驱动设计》中并没有这些概念,并不是说没有就不重要,在实现领域驱动设计中,还是非常重要的,你可以把它看作是一种思考的方式,就像你切一个西瓜,横切、竖切、还是直接用拳头爆掉,这些方式都可以,不管怎么实施,只要最后能吃到西瓜就行。对于领域专家和开发人员所建立的通用语言,到底该如何沟通,或者相互直接如何表达?我觉得探讨问题空间和解决方案空间,是一个很好的方式,你可以把他们看作是切西瓜的“刀”,很锋利,也高效。

理解限界上下文(Bounded Context)

上面的四点概念,在领域驱动设计的时候,可以不必了解,因为它只是实现领域驱动设计的一种概念方式,理解它也只不过可以让你少走些弯路,你完全可以按照自己的方式去实现,当然,偏离了大道,也怨不得别人。

限界上下文的概念很重要,我之前在做消息项目的时候,不是很了解这个概念,只是隐约记得什么限定上下文、界限上下文,然后就是实体、值对象和领域服务了,其实最准确的名字是限界上下文,限的意思就是划分、规定,界就是界限、或者一个边界,上下文就是业务的整个流程,总的来说,可以称限界上下文为业务流程在一个划定的界限中,我们知道,业务的描述是通过通用语言来表述的,限界上下文和通用语言的关系就是:在一个特定的限界上下文只使用一套通用语言,并且保证它的清晰性和简洁性。

上面的图来自《实现领域驱动设计》,这个图我们可以和上一篇进行对比下,在之前的团队开发中,是把身份与访问上下文划分到协作上下文中了,并导致了一系列的问题,协作上下文包含的内容有论坛、博客、及时消息、留言板等,但这些都不是核心域,核心域是敏捷项目管理,也就是一开始说的那个简单业务用例:待定项提交到冲刺中,协作上下文只不过是支撑子域,它的作用就是用来支撑敏捷项目管理上下文的,可以这样说,如果协作上下文出现了问题,并不影响这个项目的运行,顶多是影响某一模块的运行,比如待定项提交到冲刺中,这个业务操作完成后,会有一个消息通知,协作上下文出现了问题,消息通知发不出去,但是待定项是可以提交到冲刺中的,因为这两个业务操作分别处于不同的限界上下文中,也可以这样说,对于敏捷项目管理上下文,协作上下文是可以替换的。

那限界上下文和子域有什么关系呢?在上面图中,可以看到是一一对应的,比如通用子域对应于身份与访问上下文,但其实并不是这样,请注意那个虚线,虚线表示的意思是核心域和子域的界限,但界限中很多都是空白的,比如通用子域除了包含身份与访问上下文,还可以包含消息与通知上下文、日志记录上下文等等,同样,支撑子域也是如此。

我记得我在开发消息项目的时候,在领域层只有一个 MessageManager.Domain 项目,并且项目下有很多的文件夹,比如 Entity、Domain Service 等等,然后我就认为这个 Domain 项目,是整个消息项目的核心,并且,如果我再开发一个新的项目的时候,我也会这样做,这样有什么问题呢?好像没什么问题,因为对于消息项目,业务场景很简单,Domain 项目所代表的是整个领域层,也就是上面图中整个的概念,其实这种命名是有问题的,实体、值对象和领域服务等概念,是存在于一定的限界上下文中,而不是整个领域概念,也就是说,我当时在设计 Domain 项目的时候,就完全没有把限界上下文设计好,暴露出来最明显的一个问题,就是 Domain 项目中包含有 User 实体的概念,你明白了吧,我和作者描述的那个团队开发都犯了同一个问题。

我们再来看一张图:

上面是协作上下文所包含的内容,你可以看到有好多的聚合根、领域对象等等,对于协作上下文的开发,IDDD 作者的做法是,新建一个程序集项目,也就是我们所说的类库项目,这个每个限定上下文都互不影响,而不是像我那样包含在一个 Domain 项目中,分开开发更新也方便,如果限界上下文足够复杂,比如上面的协定上下文,包含的聚合根太多,我们也可以进行细分。还有个问题是,比如用户的概念,在博客、论坛、日历等场景中,所表达的概念是不同的,那我们的身份与访问上下文该如何进行设计,还有就是协定上下文中的用户概念改如何进行设计,这是一个很重要的问题,如果是我的话,我以前肯定会把用户的概念放在协定上下文中进行开发,因为消息项目我就是这么干的,但这样造成的问题也是很严重的。

对于上面所描述的问题,我们来分析一下,不管在博客、论坛、日历等场景中,用户的概念是唯一的,也就是说它必须是唯一标识的,不能有两个同样的用户同时存在,这是首要基本条件,还有就是,用户的一些基本属性,比如用户名、邮箱、密码等等,这些在不同的场景中都是可以确定的,也都是同样存在的,对待这些共有属性,我们可以抽离出来,除了属性之外,还有一些业务操作也是公用的,比如身份验证操作,我们也同样抽离出来,对于这些抽离出来的属性和操作,我们应该在哪边进行实现?该如何实现?是在协定上下文中吗?不是,我们应该把这些用户属性和操作放在身份与访问上下文中,并进行隔离实现,为什么要进行隔离?因为身份与访问上下文是在通用子域中,也就是说并不是在支撑子域中,通用子域和核心域、其他支撑子域都有联系,也就是说,不要把协定上下文中所包含的独有用户概念,放到身份与访问上下文中进行开发,如果这样做,那么身份与访问上下文就不是通用子域了,而变成了协定上下文的一个附属上下文。

一个模型应该要与一个上下文相适应,上下文可能是指一段代码,也可能是指特定团队的工作,如果一个模型是在一次头脑风暴会议上诞生的,那么它的上下文就可能会限制在这些讨论的范围中,在有特定意义的模型中,不管模型的上下文是什么,必须要说明模型中的术语是什么意思。以上是《领域驱动设计》中,关于模型和上下文的内容,注意,上面所说的上下文并不是限界上下文,上面所说的上下文可以是一段通用语言的表述,也可以是一段代码,如果概括的话,可以认为是限界上下文的一部分。

关于限界上下文,我只是了解冰山一角,有太多的内容需要进行探讨学习,但不可否认,限界上下文是领域驱动设计中,最重要的概念之一,可以称之为最重要的首要概念,因为它是领域驱动设计的开始,自己肚里知识有限,我希望后面可以再次对这部分内容进行补充,最后,引用《领域驱动设计》中的一段描述:

  • 细胞膜不仅能把细胞内部和外部区分开来,而且还能决定通过的物质。

有人会说,你搞这么多的概念有什么用?还不如直接实践来的有用,但有时候,你会发现,实践是建立在一定基础之上的。