从壹开始微服务 [ DDD ] 之三 ║ 简单说说:领域、子域、限界上下文
前言
哈喽大家好,DDD领域驱动设计系列又开始了,前天周二的那篇入门文章中,也收到了一定的效果(写小说的除外),同时我也是倍感鸭梨,怎么说呢,DDD领域驱动设计已经有十年历史了,甚至更久,但是包括我在内的一批技术人员还是对其不是很明白,这几天我也是日思夜想,怎样才能说的明白,怎样才能把这个高高在上的思想落在实践上,可惜的是国内栗子比较少,国外文章比较少,只能硬啃了,所以更需要大家一起来讨论,这里要说一下,是一起讨论推动,而不是内心去拒绝,而一直和多层架构做对比,这样不仅不利于学习,也无法带动我的积极性,所以,这里恳请大家,多多评论,多多交流,比较我一个人很难扛得动这个DDD的大旗。
好啦,言归正传,上次咱们说到了《[ DDD ] 之二 ║ DDD入门 & 项目结构粗搭建》,其中主要说明了为什么使用DDD,以及如何简单的搭建一个基于领域的粗略层,颗粒度还是项目级别,还没有继续往下深究,今天呢,咱们就往下慢慢走,说一说整个项目下,是如何实现领域设计的。
这里先给大家提一个问题,如果一个新的项目,比如一个小的问答系统交给你的手里,PM 刚刚和你简单的讨论了下需求,下一步你打算做些什么?
1、根据需求,立刻准备设计数据库,建表,脑中模拟场景;
2、根据需求,立刻建立实体类(也就是model层),然后CodeFirst 生成数据库;
3、找寻该领域专家(做过或者懂得类似产品的人),设计该问答领域下,有哪些子领域,制作限界上下文;
4、啥都没有,直接网上找开源项目,下载下来看看;
老张说:这里没有正确与否的比较,只是一个习惯和优劣的分析,不用太在意,如果你比较好奇,那就往下看吧。
零、今天要完成绿色的部分
一、领域 —— 就是一个独立项目
1、领域的概念
这个概念相信很多人已经很明白了,而且也听到了无数遍,这里就再简单的说两句:
领域(Domain)其实就是一个组织所要做的整个事情,已经这个事情下所包含的一切内容。这是一个范围概念,而且是面向业务的(注意这里不是面向技术的,更不是面向数据库的持久化的),每个组织都有自己的人员、自己的工作业务范围和做事方式,当你为该组织开发软件的时候,你面对的就是这个组织的领域。
就比如之前我在一家旅游公司进行开发工作,那我所进行的开发工作就是一个旅游行业,我必须要很清晰旅游行业的其中的领域知识,而且必须能和领域专家通过通用性语言进行沟通,这样能保证我开发出来的是他们想要的,而不是我单纯的从技术上实现,在领域设计上一塌糊涂。当然我们每天也都在做这样的事情,也许你感觉很正常,那我再举个例子:
我在开发其中一个目的地(旅游景点)项目的时候,这是一个领域,后来在电商系统项目中,又是一个领域,但是在电商领域中,涉及到了景点领域的一些数据,那我如果不和领域专家沟通,有时候为了贪图技术上的方便,甚至把两个领域合并成一个,虽然都不大,合并以后大小也还可以,但是这样却完全打破了领域的这个概念,这个就是完全面向技术开发的,因为领域专家看不懂我这么写到底属于什么。
当然上边的栗子有点儿牵强,咱们再说下以后我想做的一个基于DDD的问答项目,咱们先画一个框。
就如图所示,咱们首先定义一个边界,至于里边有什么东西,咱们接着往下看,这个很简单。
2、如何定义一个领域
这个是更简单的一个问题,在领域设计中,有两个方法:战略设计和战术设计,其实我个人感觉可以定义两步走,这两个是有先后之分的,
战略设计中定义了,一个领域就是一个问题空间,我们在业务中所遇到的所有的问题与挑战;
在战术设计中,一个领域就算一个解决问题空间,用来解决在问题空间的所有问题;
所以,其实一个领域就是一个我们建立的一个解决方案,一个项目,在我们的问答项目中,整个解决方案就是一个问答领域。
二、子领域 —— 具体的项目实现
1、子域 / 核心子领域 / 通用子领域
什么是子域(SubDomain)呢?这个很好理解,就是在整个领域中,我们如何对其进行拆分,然后满足我们的业务逻辑。一个子域可能是一个 dll ,一个命名空间的形式存在。
我们定义好领域,并且划分好限界后,就开始考虑如何进行实现,这里大家想一想如何设计与划分,这里就说说我自己的之前的想法:
在我们的问答领域设计中,我们的思路一定是有客户来 =》验证是否有发问题的权限 =》 然后发布一个问题 =》
这仅仅是一个发布问题的流程,也仅仅是一个顾客认证的过程,很简单,我们一般会怎么分子领域呢,可能会这么分,这个就是 消息发布子领域,里边有我们的发布模型,用户模型,讨论模型,日历模型等等,大概就是这个样子
因为我们会这么想:“用户和权限这两个模型,和我们的消息子领域有何紧密的关系,你看,发布+回复+讨论+日历(指自己新建一个日历功能,具体待定),这些模型肯定都需要用户登陆认证吧,甚至有些是需要授权的,分在一个子领域有什么不对么?”,这样的代码逻辑应该是这样的
如果是你看到这里,首先明白了什么是子领域了吧,也知道如何划分了,但是你感觉这个划分对么? 如果你感觉很正常,那就请往下看吧。
2、核心子领域 / 通用子领域 / 支撑子领域
我们再来分析一下,我们的问答领域中的有哪些内容,首先:肯定有消息发布子领域,这个也是上边说到的,这个毋庸置疑,一个问答系统,消息发布是肯定的(这里说明下:发布问题,回答问题,讨论问题等都属于一个消息的发布,这个应该理解),而且这个子领域是缺少它不可的,这个就是我们的核心子领域。
再来看看,还有一些其他的,比如日志记录,数据操作痕迹记录(哪个管理员修改了哪些数据),这些子领域贯穿着我们整个领域系统,被其他领域共用,我们称之为 通用子领域,
当然,我们还有一些站内的即时消息,wiki百科,通知提醒,活动跟踪,等等,这些都不是我们的核心子域,因为没有这些,我们依然可以进行问答,但是这些确是支撑着我们核心子域的相关功能,我们就把这些命名为 支撑子领域,这个时候你会问,这些支持子领域要不要再拆开,我个人表示没有很大的必要。
最后我们再来看看我们上边的用户认证授权问题,在上边我们把他们柔和到了消息核心子域里,但是这里要说明,这两者是没有关系的:
为什么没有关系呢?诚然,我们的软件是必须有用户参与的,但是我们应该将不同的用户种类区别对待,因为在不同的上下文(下边会说到)中,他们的作用和任务是不一样的,在消息核心子域中,我们关注的是角色,不管他是谁或者有什么权限,如果我们有一天把权限模型修改了,那我们的问答模型也一定要修改,你想想是不是,因为两者业务逻辑已经耦合了!
这个时候我们应该明白,发布信息和“谁可以发,在什么条件下发”其实没有太大的关系,我的问答,只关心的是“有一个顾客发布了一个问题”这样就可以了,我们关心的是发布消息这个过程,而不能把用户权限涉及进来,这个时候我们应该把用户权限单拿出来一个子领域,就叫安全子领域。
3、隔离内核
其实上边说的可能有点儿朦胧,但是我们应该都已经用到了,如果你看了我的上一个系列教程,你应该知道有一个JWT权限验证那一章节,很多人就是不很理解,是如何进行授权验证的,其实采用的就是隔离内容,以前我们写逻辑,就算直接在控制器里,判断当前用户权限,但是现在我们是通过一个中间件,判断 Token 所包含的用户Role 是否有这个权限,再进行下一步,只不过在DDD中,把这一块单拿出来形成了一个安全子领域了,这个时候你应该明白了吧。
/// <summary>
/// 删除一个顾客信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost, ActionName("Delete")]
[Authorize(Policy = "CanRemoveCustomerData")]
[Route("customer-management/remove-customer/{id:guid}")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(Guid id)
{
_customerAppService.Remove(id); if (!IsValidOperation()) return View(_customerAppService.GetById(id)); ViewBag.Sucesso = "Customer Removed!";
return RedirectToAction("Index");
}
这个时候,可能还不是很明白,为什么好好的程序要拆分,这么做的目的又是为了什么,直接在需要用到的权限的地方写业务逻辑不就行了么,这个往下看,咱们说说限界上下文。
三、限界上下文 —— 领域模型的边界
1、限界上下文是显示的,有语义的
限界上下文(Bounded Context)定义了每个模型的应用范围,在每个Bounded Context中确保领域模型的一致性。不同的限界上下文中,领域模型可以不用保证一致性。通常我们根据团队的组织、软件系统的每个部分的用法及物理表现(如组件划分,数据库模式)来设置模型的边界。
概念还是有点儿朦胧,那就举例来说:
在电商系统中,销售子域是核心域,商品子域和物流子域为支撑子域。在这三个子域中,都要和商品打交道。如果把商品抽象为Product对象的话,按我们一般的常规思路(抛开子域的划分)来说,不管是商品销售还是发货,我们都可以共用同一个Product对象。
但在DDD中,在商品子域和销售子域中,可以共享这个Product对象,但在物流子域,就有点大材小用。为什么呢?因为毕竟物流子域关注的是商品的发货处理和物流跟踪。针对发货流程而言,我只关心商品的数量、大小、重量等规格,而不必了解商品的价格等其他信息。所以说物流子域应该关注的是货物的发货处理而不是商品。
那为什么我们之前的开发思路会共用同一个Product对象呢?
答案很简单,没有进行领域的划分。把整个项目一概而论,统一建模导致的结果。
在DDD的思想下,当划分子域之后,每个子域都对应有各自的上下文。在销售子域和商品子域所在的上下文语境中,商品就是商品,无二义性。在物流子域的上下文语境中,我们也可以说商品的发货处理,但这时的商品就特指货物了。确定了真实面目之后,我想我们也会不由自主的抽象一个新的Cargo对象来处理物流相关的业务。这也是DDD带来的好处,让我们更清晰的建模。
2、定义限界上下文
在我们上边的子域定义中,我们出现了三个子域,我这里同时也定义了三个限界上下文(这里说下,两者不是一对一的关系),总体来说,我们不应该按技术架构或者开发任务来创建限界上下文,应该按照语义的边界来考虑。
我们的实践是,考虑产品所讲的通用语言,从中提取一些术语称之为概念对象,寻找对象之间的联系;或者从需求里提取一些动词,观察动词和对象之间的关系;我们将紧耦合的各自圈在一起,观察他们内在的联系,从而形成对应的界限上下文。形成之后,我们可以尝试用语言来描述下界限上下文的职责,看它是否清晰、准确、简洁和完整。简言之,限界上下文应该从需求出发,按领域划分。
3、上下文都包含哪些内容
一个限界上下文不是只有领域模型,当然这个是必不可少的,它总体来说是一个系统,一个应用程序,或者一个业务服务,它里边会有实体,值对象,领域事件(一个个方法事件组成,比如用户注册,修改密码,验证信息等等都是该上下文中的领域事件),在我们的身份和访问上下文中,是这样定义的
感觉写到这里还是没有写的很透彻,因为我们还没有涉及到代码,可能通过代码的设计会比较好。
四、结语
本文主要是通过DDD领域设计的思想,来说明如何对一个项目进行细分的过程,这个再想想文章开头提出的问题,是不是稍微有些感触,只不过在没有代码的讲解下,一起总是很空洞,下次咱们直接通过基础设施层中的上下文定义,来进一步了解领域设计的思想吧。
从壹开始微服务 [ DDD ] 之三 ║ 简单说说:领域、子域、限界上下文的更多相关文章
- 从壹开始微服务 [ DDD ] 之八 ║剪不断理还乱的 值对象和Dto
缘起 哈喽大家周四好,时间是过的真快,这几天一直忙着在公司的项目,然后带带新人,眼看这周要过去了,还是要抽出时间学习学习,这些天看到群里的小伙伴也都在忙着新学习,还是很开心的,至少当时的初衷已经达到了 ...
- 从壹开始微服务 [ DDD ] 之六 ║聚合 与 聚合根 (下)
前言 哈喽大家周二好,上次咱们说到了实体与值对象的简单知识,相信大家也是稍微有些了解,其实实体咱们平时用的很多了,基本可以和数据库表进行联系,只不过值对象可能不是很熟悉,值对象简单来说就是在DDD领域 ...
- 从壹开始微服务 [ DDD ] 之七 ║项目第一次实现 & CQRS初探
前言 哈喽大家周五好,我们又见面了,感谢大家在这个周五读我的文章,经过了三周的时间,当然每周两篇的速度的情况下,咱们简单说了下DDD领域驱动设计的第一部分,主要包括了,<项目入门DDD架构浅析& ...
- 从壹开始微服务 [ DDD ] 之终篇 ║当事件溯源 遇上 粉丝活动
回首 哈喽~大家好,时间过的真快,关于DDD领域驱动设计的讲解基本就差不多了,本来想着周四再开一篇,感觉没有太多的内容了,剩下的一个就是验证的问题,就和之前的JWT很类似,就不打开一个章节了,而且这个 ...
- 从壹开始微服务 [ DDD ] 之十二 ║ 核心篇【下】:事件驱动EDA 详解
缘起 哈喽大家好,又是周二了,时间很快,我的第二个系列DDD领域驱动设计讲解已经接近尾声了,除了今天的时间驱动EDA(也有可能是两篇),然后就是下一篇的事件回溯,就剩下最后的权限验证了,然后就完结了, ...
- 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)
缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...
- 从壹开始微服务 [ DDD ] 之九 ║从军事故事中,明白领域命令验证(上)
烽烟 哈喽大家周二好呀,咱们又见面了,上周末掐指一算,距离 圣诞节 只有 5 周的时间了(如果你还不知道为啥我要提圣诞节这个时间点,可以看看我的第二系列开篇<之一 ║ D3模式设计初探 与 我的 ...
- 从壹开始微服务 [ DDD ] 之一 ║ D3模式设计初探 与 我的计划书
缘起 哈喽大家周四好!又是开心的一天,时间过的真快,我们的 <从壹开始 .net core 2.1 + vue 2.5>前后端分离系列共 34 篇已经完结了,当然以后肯定还会有更新和修改, ...
- 从壹开始微服务 [ DDD ] 之十 ║领域驱动【实战篇·中】:命令总线Bus分发(一)
烽火 哈喽大家好,老张又见面了,这两天被各个平台的“鸡汤贴”差点乱了心神,博客园如此,简书亦如此,还好群里小伙伴及时提醒,路还很长,这些小事儿就随风而去吧,这周本不打算更了,但是被群里小伙伴“催稿”了 ...
随机推荐
- Prometheus监控数据格式学习
本文大纲: • prometheus metrics的概念• k/v的数据形式• prometheus exporter的使⽤(pull形式采集数据)• prometheus pushgateway的 ...
- 面试时怎样回答:你对原生ajax的理解
很多人跟我一样用习惯了jq封装好的$.ajax,但是面试时,原生ajax是很多面试官喜欢问的问题,今天再查资料,打算好好整理一下自己理解的原生ajax. 首先,jq的ajax:一般我常用的参数就是这些 ...
- 打开office时提示错误窗口“向程序发送命令时出现问题”的解决方案
今天同事问了我一件很怪异的事情,说她的office打不开了,如打开word或excel时,突然出现错误提示错误窗口"向程序发送命令时出现问题",分析原因才知道她安装了 AVG pc ...
- MFC中打开选择文件夹对话框,并将选中的文件夹地址显示在编辑框中
一般用于选择你要将文件保存到那个目录下,此程序还包含新建文件夹功能 BROWSEINFO bi; ZeroMemory(&bi, sizeof(BROWSEINFO)); //指定存放文件的 ...
- codeForces 472D 最小生成树
题目大意:给出一个图中点的两两距离,问是否是一棵树,若是,求出平均边权最大的点 prim最小生成树,若原图是树,则最小生成树的距离就是原距离.否则不是. 搞出来树了,第二问随便dfs就好了. #inc ...
- ArcGIS API for JavaScript 入门教程[1] 渊源
->对于萌新,你可能需要了解一下这个东西是什么 ->对于已经知道要用这个东西的开发者,你可能需要了解一下它的底层机制 不针对大牛.龟速更新ing. 转载注明出处.博客园&CSDN& ...
- Arrays.asList 为什么不能 add 或者 remove 而 ArrayList 可以
分析如下例子: 1 import java.util.Arrays; 2 import java.util.List; 3 4 5 public class Test { 6 public stati ...
- EffictiveC++笔记 第3章
Chapter 3 资源管理 条款13: 以对象管理资源 有时即使你顺利地写了对应对象的delete语句,但是前面的区域可能会有一个过早的return语句或者抛出了异常.它们一旦执行,控制流绝不会触及 ...
- JavaScript使用闭包实现单例模式
闭包是JS的一种特性,其中一点就是:可以将外部函数的变量保存在内存中,利用这一特性,我们可以用来实现类的单例模式. 首先需要了解何为单例模式: 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问 ...
- 一行代码实现数组去重(ES6)
ES6中新增了Set数据结构,类似于数组,但是 它的成员都是唯一的 ,其构造函数可以接受一个数组作为参数,如: let array = [1, 1, 1, 1, 2, 3, 4, 4, 5, 3]; ...