[转]浅析DDD(领域驱动设计)

 

最近在做一些微服务相关的设计,内容包括服务的划分,Restful API的设计等。其中比较棘手的就是Service的职责划分:如何抽象具有统一业务范畴的Model,使其模块化,又如何高度提炼并组合多模块,使得业务可独立服务化。为了找寻答案,看了不少书籍和博客,在DDD中找到了一些思路,个人觉得受益匪浅,或许也可以受用于大家,特分享于此。

 

什么是DDD

 

软件开发不是一蹴而就的事情,我们不可能在不了解产品(或行业领域)的前提下进行软件开发,在开发前,通常需要进行大量的业务知识梳理,而后到达软件设计的层面,最后才是开发。而在业务知识梳理的过程中,我们必然会形成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念。

 

听起来这和传统意义的软件开发没啥区别,只是换了点新鲜的名词而已,其实不然。

 

软件开发 VS DDD

 

一般软件设计或者说软件开发分两种:瀑布式敏捷式

 

前者一般是项目经理经过大量的业务分析后,会基于现有需求整理出一个基本模型,再将结果传递给开发人员,这就是开发人员的需求文档,他们只需要照此开发便是。这种模式下,是很难频繁的从用户那里得到反馈,因此在前期分析时就已经默认了这个业务模型是正确的,那么结果可想而之,数月甚至数年后交付的时候,必然和客户的预期差距较大。

 

后者在此基础上进行了改进,它也需要大量的分析,范围会设计到更精细的业务模块,它是小步迭代,周期性交付,那么获取客户的反馈也就比较频繁和及时。可敏捷也不能够将业务中的方方面面都考虑到,并且敏捷是拥抱变化的,大量的需求或者业务模型变更必将带来不小的维护成本,同时,对人(Developer)的要求也必然会更高。

 

DDD则不同:它像是更小粒度的迭代设计,它的最小单元是领域模型(Domain Model),所谓领域模型就是能够精确反映领域中某一知识元素的载体,这种知识的获取需要通过与领域专家(Domain Expert)进行频繁的沟通才能将专业知识转化为领域模型。领域模型无关技术,具有高度的业务抽象性,它能够精确的描述领域中的知识体系;同时它也是独立的,我们还需要学会如何让它具有表达性,让模型彼此之间建立关系,形成完整的领域架构。通常我们可以用象形图或一种通用的语言(Ubiquitous Language)去描述它们之间的关系。在此之上,我们就可以进行领域中的代码设计(Domain Code Design)。如果将软件设计比做是造一座房子,那么领域代码设计就好比是贴壁纸。前者已经将房子的蓝图框架规划好,而后者只是一个小部分的设计:如果墙纸贴错了,我们可以重来,可如果房子结构设计错了,那可就悲剧了。

 

建立领域知识(Build Domain Model)

 

说了这么多领域模型的概念,到底什么是领域模型呢?以飞机航行为例子:

 

现要为航空公司开发一款能够为飞机提供导航,保证无路线冲突监控软件。那我们应该从哪里开始下手呢?根据DDD的思路,我们第一步是建立领域知识:作为平时管理和维护机场飞行秩序的工作人员来说,他们自然就是这个领域的专家,我们第一个目标就是与他们沟通,也许我们并不能从中获取所有想要的知识,但至少可以筛选出主要的内容和元素。你可能会听到诸如起飞,着陆,飞行冲突,延误等领域名词,让们从一个简单的例子开始(就算是错误的也没关系):

 

  • 起点->飞机->终点

 

这个模型很直接,但有点过于简单,因为我们无法看出飞机在空中做了什么,也无法得知飞机怎么从起点到的终点,刚才我们似乎提到无路线冲突,那么如此似乎会好些:

 

  • 飞机->路线->起点/终点

 

既然点构成线,那何不:

 

  • 飞机->路线->points(含起点,终点)

 

这个过程,是我们不断建立领域知识的过程,其中的重点就是寻找领域专家频繁沟通,从中提炼必要领域元素。

 

尽管看起来还是很简单,但我们已经开始一步步的在建立领域对象和领域模型了。

 

通用语言(Ubiquitous Language)

 

上面的例子的确看起来简单,但过程并非容易:我们(开发人员)和领域专家在沟通的过程中是存在天然屏障的:我们满脑子都是类,方法,设计模式,算法,继承,封装,多态,如何面向对象等等;这些领域专家是不懂的,他们只知道飞机故障,经纬度,航班路线等专业术语。

 

所以,在建立领域知识的时候,我们(开发人员和领域专家)必须要交换知识,知识的范围范围涉及领域模型的各个元素,如果一方对模型的描述令对方感到困惑,那么应该立刻换一种描述方式,直到双方都能够接受并且理解为止。在这一过程中,就需要建立一种通用语言,作为开发人员和领域专家的沟通桥梁。

 

可如何形成这种通用语言呢?其实答案并不唯一,确切的说也没有什么标准答案。

 

a)UML
利用UML可以清晰的表现类,并且展示它们之间的关系。但是一旦聚合关系复杂,UML叶子节点将会变的十分庞大,可能就没有那么直观易懂了。最重要的是,它无法精确的描述类的行为。为了弥补这种缺陷,可以为具体的行为部分补充必要说明(可以是标签或者文档),但这往往又很耗时,而且更新维护起来十分不便。
b)伪代码
极限编程是推荐这么做的,这个办法对程序猿来说固然好,可立刻就要将现有模型映射到代码层面,这对人的要求也是不低,并不容易实现。

 

模型驱动设计(Domain Driven Design)

 

模型关系图(Model-Driven Design)

 

领域驱动设计中的模型关系图如下:

 

 

层结构(Layered Architecture)

 

 

  • User Interface

 

负责向用户展现信息,并且会解析用户行为,即常说的展现层。

 

  • Application Layer

 

应用层没有任何的业务逻辑代码,它很简单,它主要为程序提供任务处理。

 

  • Domain Layer

 

这一层包含有关领域的信息,是业务的核心,领域模型的状态都直接或间接(持久化至数据库)存储在这一层。

 

  • Infrastructure Layer

 

为其他层提供底层依赖操作。

 

层结构的划分是很有必要的,只有清晰的结构,那么最终的领域设计才宜用,比如用户要预定航班,向Application Layer的service发起请求,而后Domain Layler从Infrastructure Layer获取领域对象,校验通过后会更新用户状态,最后再次通过Infratructure Layer持久化到数据库中。

 

实体(Entity) & 值对象(Value Object)

 

实体与面向对象中的概念类似,在这里再次提出是因为它是领域模型的基本元素。在领域模型中,实体应该具有唯一的标识符,从设计的一开始就应该考虑实体,决定是否建立一个实体也是十分重要的。

 

值对象和我们说的编程中数值类型的变量是不同的,它仅仅是没有唯一标识符的实体,比如有两个收获地址的信息完全一样,那它就是值对象,并不是实体。值对象在领域模型中是可以被共享的,他们应该是“不可变的”(只读的),当有其他地方需要用到值对象时,可以将它的副本作为参数传递。

 

服务(Services)

 

当我们在分析某一领域时,一直在尝试如何将信息转化为领域模型,但并非所有的点我们都能用Model来涵盖。对象应当有属性,状态和行为,但有时领域中有一些行为是无法映射到具体的对象中的,我们也不能强行将其放入在某一个模型对象中,而将其单独作为一个方法又没有地方,此时就需要服务.

 

服务是无状态的,对象是有状态的。所谓状态,就是对象的基本属性:高矮胖瘦,年轻漂亮。服务本身也是对象,但它却没有属性(只有行为),因此说是无状态的。

 

PS:这与我们常说的服务器的状态是两个概念,无状态的服务器是指,对服务器来说每次接收到的HTTP请求都像是客户端第一次发送的一样;而有状态的服务器就会存储客户端的状态,常见的就是Cookie&Session

 

服务存在的目的就是为领域提供简单的方法。为了提供大量便捷的方法,自然要关联许多领域模型,所以说,行为(Action)天生就应该存在于服务中。

 

服务具有以下特点:

 

a)服务中体现的行为一定是不属于任何实体和值对象的,但它属于领域模型的范围内
b)服务的行为一定设计其他多个对象
c)服务的操作是无状态的

 

PS:不要随意放置服务,如果该行为是属于应用层的,那就应该放在那;如果它为领域模型服务,那它就应该存储在领域层中,要避免业务的服务直接操作数据库,最好通过DAO。

 

模块(Moudles)

 

对于一个复杂的应用来说,领域模型将会变的越来越大,以至于很难去描述和理解,更别提模型之间的关系了。模块的出现,就是为了组织统一的模型概念来达到减少复杂性的目的的。而另一个原因则是模块可以提高代码质量和可维护性,比如我们常说的高内聚,低耦合就是要提倡将相关的类内聚在一起实现模块化。

 

模块应当有对外的统一接口供其他模块调用,比如有三个对象在模块a中,那么模块b不应该直接操作这三个对象,而是操作暴露的接口。模块的命名也很有讲究,最好能够深层次反映领域模型。

 

聚合(Aggregates)

 

聚合被看作是多个模型单元间的组合,它定义了模型的关系和边界。每个聚合都有一个根,根是一个实体,并且是唯一可被外访问的。正是如此,聚合可以保证多个模型单元的不变性,因为其他模型都参考聚合的根。所以要想改变其他对象,只能通过聚合的根去操作。根如果没有了,那么聚合中的其他对象也将不存在。
一个简单的例子如下:

 

 

customer是该聚合的根,其他的都是内部对象,如果外部需要用户地址,拷贝一份传递出去即可。显而易见,用户如果不存在,其他信息均无意义。

 

工厂(Factories)

 

在大型系统中,实体和聚合通常是很复杂的,这就导致了很难去通过构造器来创建对象。工厂就决解了这个问题,它把创建对象的细节封装起来,巧妙的实现了依赖反转。当然对聚合也适用(当建立了聚合根时,其他对象可以自动创建)。工厂最早被大家熟知可能还是在设计模式中,的确,在这里提到的工厂也是这个概念。

 

但是不要盲目的去应用工厂,以下场景不需要工厂:
a)构造器很简单
b)构造对象时不依赖于其他对象的创建
c)用策略模式就可以解决

 

仓库(Repository)

 

仓库封装了获取对象的逻辑,领域对象无须和底层数据库交互,它只需要从仓库中获取对象即可。仓库可以存储对象的引用,当一个对象被创建后,它可能会被存储到仓库中,那么下次就可以从仓库取。如果用户请求的数据没在仓库中,则会从数据库里取,这就减少了底层交互的次数。当然,仓库获取对象也是有策略的,如下:

 

 

PS:仓库看起来有些像Infrastructure Layer的东西,但其实不然,仓库更像是本地缓存,需要时才会访问数据库

 

结束语

 

CQRS本身也是一种架构模式,但更多的是它被应用在DDD中。因为DDD中有工厂仓库来管理领域模型,前者主要用于创建,而后者则用于存储。这就表明在DDD中是默认将读写分离的,DDD似乎就天生和CQRS有着无缝的链接。

 

CQRS往往要求数据库进行读写分离,具体来说,所有的更新操作均无返回值(void),而读操作才返回对应的值。在实现CQRS时,又和事件源(Event Source)相结合,以下是一个简单的交互过程:

 

客户端发起一个请求,服务端将其映射为一个命令,该命令会从仓库中读取一个相关的聚合,对该聚合进行操作,将会生成一个事件源,将该事件发送出去,接收方收到消息后(并不是立刻)将会更新领域对象,完成一次更新操作。

 

在此基础上,还有称之为六边形的架构风格,它将DDD的领域模型包裹在内,外围含有多种适配器来适配各种通信方式,总体来说,我觉得无论是DDD,CQRS还是六边形,都是一种架构的设计思路,没有绝对的优势,同时也有各自的复杂度,并不容易理解,但有时在软件设计时,不妨多学习一下其中的小细节和思路,必然能够有所收获。

 

至于能否应用?如何应用?,笔者只能说不能生搬硬套,需要有一定的实践经验才能去尝试,一般情况下,结合项目特点,能适当的灵活采用其中的设计思路即可。

 

参考资料:

 

Domain Driven Design Quickly

 

领域驱动设计(DDD)实现之路

 

DDD领域驱动设计基本理论知识总结

 

浅谈命令查询职责分离(CQRS)模式

 

你应该知道的四种优秀架构


---------------------
作者:喜欢特别冷的冬天下着雪
来源:CSDN
原文:https://blog.csdn.net/kkkkkxiaofei/article/details/62237121
版权声明:本文为作者原创文章,转载请附上博文链接!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/285676.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PhotoScan精品教程】任务三:生成密集点云、生成网格、生成DEM、生成DOM

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) 【PhotoScan精品教程】任务一:新建工程、导入照片、设置坐标系、导入控制点(POS)、自由空三 【PhotoScan精品教程】任务二:刺像控点,平差,质量报告精度检查,像控点POS权重调配 【PhotoScan…

windows环境实现批量加密文件,并创建加密文件同名(不带后缀)的文件夹,然后把加密文件和图片和文本放入这个文件夹。

1、 需求 我想把资源文件先加密成压缩文件,然后同时创建每个加密压缩文件同名的文件夹,同时需要把这个加密文件拷贝到这个同名的文件夹,然后还需要把一个图片和一个文本文档同时放进这个文件夹,然后在不加密压缩这个文件夹&#…

.NET7之MiniAPI(特别篇) :Preview5优化了JWT验证(上)

在.NET7的Preview5中,优化了asp.net core中的JWT验证,不用像以前繁琐了,更重要的是带来了一组生成Token的工具,可以让开发人员或测试人员不需登录获取Token,而达到测试的目的。创建项目现在来看一下怎么使用&#xff0…

iOS - UTI

一、UTI概念 1、什么是UTI Uniform Type Identifier,是字符串,格式标识符。 根据UTI,可得到相应的其他类型的格式标识符。比如public.jpeg对应于: A four-character file type code (an OSType) of JPEGA filename extension of .…

【Inpho精品教程】任务一:Inpho预处理准备(Pix4d生成未畸变图像、Pix4d生成相机参数文件)

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) Inpho软对于初学者来说,可能不像pix4d、PhotoScan等那么得心应手,处理过程也稍微有些复杂。Inpho处理的是POS分离的照片,在新建工程之前,需要获取相机参数、未畸变图像等,而这些前期的工作一…

学习MongoDB(三) Add an Arbiter to Replica Set 集群中加入仲裁节点

Add an Arbiter to Replica Set 在集群中加入仲裁节点,当集群中主节点挂掉后负责选出新的主节点,仲裁节点也是一个mongo实力,但是它不存储数据。 1、仲裁节点消耗很小的资源,而且不需要专用的服务器。 2、不能把仲裁节点安装到集…

[转]new Thread的弊端及Java四种线程池的使用

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用。本文是基础篇,后面会分享下线程池一些高级功能。 1、new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() {Overridepublic void run() …

【Inpho精品教程】任务二:Inpho创建工程(创建项目、新建相机参数、导入照片、导入POS、生成航条、保存项目)

《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho) 在任务一中,我们做了Inpho创建工程前的数据预处理工作,获取了未畸变的图像、相机参数、Inpho支持的POS等数据,本文在此基础之上,创建Inpho工程,主要步骤有:创建项目、新建相机参数、导入照片…

return view详解

1、return View(); 返回值 类型:System.Web.Mvc.ViewResult将视图呈现给响应的 View() 结果。 注释 View() 类的此方法重载将返回一个具有空 ViewName 属性的 ViewResult 对象。 如果你正在编写控制器操作的单元测试,则需为那些不采用字符串视图名称的单…

C语言试题九十一之写一个程序,用公式π/4=1-1/3+1/5-1/7+...,求π的近似值,直到最后一项的绝对值小于10^-8为止。

1、题目 写一个程序,用公式π/4=1-1/3+1/5-1/7+...,求π的近似值,直到最后一项的绝对值小于10^-8为止。 2 、温馨提示 C语言试题汇总里可用于计算机二级C语言笔试、机试、研究生复试中C程序设计科目、帮助C语言学者打好程序基础、C语言基础,锻炼您的逻辑思维和解决问…

C# 切换中英文输入法

本文经原作者授权以原创方式二次分享,欢迎转载、分享。原文作者:唐宋元明清原文地址:https://www.cnblogs.com/kybs0/p/10298697.htmlC# 切换中英文输入法在界面输入时,有时需要限定输入法。在不自定义正则表达式或者其它输入处理…

[转].NET 开源项目 Polly 介绍

今天介绍一个 .NET 开源库:Polly,它是支持 .NET Core 的,目前在 GitHub 的 Star 数量已经接近 5 千,它是一个强大且实用的 .NET 库。 Polly 介绍 官方对 Polly 的介绍是这样的: Polly is a .NET resilience and tran…

「 刘一哥GIS」CSDN专业技术博文专栏目录索引

刘一哥GIS 个人简介:刘一哥,多年研究地图学、地理信息系统、遥感、摄影测量和GPS等应用,精通ArcGIS等软件的应用,精通多门编程语言,擅长GIS二次开发和数据库系统开发,具有丰富的行业经验,致力于…

memcached 和 redis 的区别与选择

没有必要过多的关注性能。由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储…

C语言试题九十之实现输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 输入一行字…

数据库备份需要注意的

2019独角兽企业重金招聘Python工程师标准>>> 1、PHPMYADMIN无法导出大数据表的 如果你的数据库中有上百个数据表,并且有很多数据表记录都超过了1G,还有很多INNODB数据表,这个时候用PHPMYADMIN导出,你就会发现恢复后可能…

在 .NET 6 中使用 dotnet format 格式化代码

我不得不承认,在 code review 的时候,我花费了很多时间来研究 C# 的代码格式问题,这是没有太大意义的工作,我应该专注于其他事情,而不是观察同事是否忘记格式化代码,或者是使用了其他不同规则的代码编辑器。…

【天工Godwork精品教程】任务一:创建工程(导入相片、编辑相机参数、导入POS)

无人机航测精品专栏链接:《无人机航空摄影测量精品教程》 【天工Godwork精品教程】任务一:创建工程(导入相片、编辑相机参数、导入POS) 【天工Godwork精品教程】任务二:导入控制点、POS权重设置、自由空三 【天工Godwork精品教程】任务三:刺像控点、空三平差、精度优化调…

[转]数据库事务ACID特性

ACID特性 数据库管理系统中事务(transaction)的四个特性(分析时根据首字母缩写依次解释):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性&#xff…

dotnet-exec 0.4.0 released

dotnet-exec 0.4.0 releasedIntrodotnet-exec 是一个 C# 程序的命令行小工具,可以用来运行一些简单的 C# 程序而无需创建项目文件,而且可以自定义项目的入口方法,支持但不限于 Main 方法Install/Updatedotnet-exec 是一个 dotnet tool&#x…