领域驱动设计:软件核心复杂性应对之道_人人都可以领域驱动设计(一)

cd81d8e93378cac1c81eaca923caa964.png

最近几年,领域驱动设计(Domain-Driven Design,DDD)这个术语越来越多地出现在软件工程师的视野里。对DDD不熟悉的人可能会觉得它是软件领域里的一个新的概念,但是实际上,Eric Evans在十几年前就已经提出了这个概念。这个“古老”的概念在之所以能够重焕新生,很大程度上是因为遇上了“微服务”这个浪潮。如果把DDD里面的理念拿去和微服务架构做对比,你会发现它们有着高度的相似性——DDD里的限界上下文不正是微服务架构中的微服务吗?于是,各大公司纷纷运用DDD的方法论来构建自己的产品架构。有些团队成功地将DDD结合到了产品架构中,产生了许多优秀的实践;也有些团队反映DDD太过复杂,很难落地。那么DDD究竟是个什么样的理念?为什么大家都争先恐后地使用它,彷佛不加点DDD都不好意思说自己是微服务架构?然而又为什么那么多团队说DDD难以落地?本文将会结合简单的代码实现来谈谈笔者对DDD的理解。

什么是领域驱动设计?

软件的核心是其为用户解决领域相关的问题的能力。

软件就是为了解决某一领域相关问题而存在的,比如一个普通的计算器软件,就是为了满足我们进行简单的加减乘除运算而存在。对于计算器这种小而简单的软件,一个普通的软件工程师可能花上几天就能过设计开发出来,而且基本不会出现Bug。但是对于一些大型而且复杂的系统,一个团队都得花上很长的时间去设计整个架构,然后经过n轮迭代才能开发出可用的版本,而且后面还有各种Bug要去处理。比如证券交易系统,里面就包括了用户系统、账户系统、订单系统、撮合系统等一系列的子系统,而且其中的调用关系和业务都非常复杂。像这样一个庞大的系统,怎样才能把它设计好呢?这正是DDD要回答的问题。

领域驱动设计(DDD)是一种软件开发的方法论,旨在帮助我们设计出高质量的软件模型

在软件领域,解决复杂问题的方法不外乎是“分治”和“抽象”,DDD也是基于这两个理念建立起一套方法论。其中将一个系统划分成多个限界上下文,限界上下文中划分出多个子域,这是分治;然后在分别对各个子域进行领域建模,这是抽象。当你在设计一个业务复杂的系统却无从下手时,尝试一下DDD,说不定困难就会迎刃而解了。DDD中最核心的理念就是领域建模,可以说它提供的各种方法都是为了帮助我们设计出更能准确传达业务规则的领域模型。一个好的领域模型可以让一个系统更加健壮,可以让一个框架易用性更加好,可以让一段代码更加好维护。那么,什么样的模型才是好的领域模型?下面,我们通过一个例子来简单说明下。

什么是领域模型?
领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同事包含了数据和行为,并且表达了准确的业务含义。

日期和时间领域模型

如何设计一个日期和时间API?

首先需要对日期和时间的概念进行建模,从直觉上,我们可以将日期和时间抽象成一个对象Date。另外,时间和日期经常都需要进行格式化输出,因此我们还需要一个用于表示时间格式的对象DateFormat。为了更好地表示年月周日等概念,再抽象出一个表示日历的Calendar对象,以及表示时区的TimeZone对象。

cf641c7293aaba7a2c3170891cf5744d.png

相信到这里大家都已经知道,这正是JDK 1.1版本的日期时间API,下面我们先回顾一下它的用法:

public class TestOldDate {public static void main(String[] args) {// 获取表示当前时刻的Date对象Date date1 = new Date();// 通过Calendar等到指定日期时间的Date对象,采用当前的系统时区Calendar calendar = Calendar.getInstance(TimeZone.getDefault());calendar.set(2020, 2, 10, 0, 0, 0);Date date2 = calendar.getTime();// 进行时间比较System.out.println("date1 is after date2: " + date1.after(date2));// 进行时间的加减法,如获得昨天的这个时刻:calendar.setTime(date1);calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH) - 1);Date date3 = calendar.getTime();// 对日期格式化输出DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("date3: " + df.format(date3));}
}
/* Output:
date1 is after date2: false
date3: 2020-02-07 23:55:39
*/

如果你用习惯了JDK 1.1版本的日期时间API,可能会觉得上述例子中的用法也没有多大的问题。但是,仔细思考一下就会发现这其中的逻辑跟我们人类对时间的处理逻辑不太像,比如要对时间做加法,首先要将需要操作的Date对象设置到Calendar,然后对Calendar做加法,最后调用Calendar的接口得到结果。时间的加法难道不应该直接对Date对象加上一个时间段就行了吗?

相信很多小伙伴都会遇到这种情况,自己写出来的代码可读性不够好。这其中原因可能是对领域的理解不够深,设计出来的领域模型没能准确的表达业务逻辑(如JDK 1.1的日期时间模型),或者开发前根本就没有进行领域建模。这样容易导致采用了一种“机器思维”去进行开发,而不是按照我们平常思考问题的思维去开发。

JDK 1.8引入了全新的日期和时间API来解决老版本的API所存在的种种问题,下面,我们来看一下比之前更准确地表达日期和时间的领域模型:

99a886debe62164774ef3e327943e5c6.png

JDK 1.8的日期和时间领域模型中的领域知识明显比老版本的领域模型要丰富很多,而且模型更加符合人类思考日期和时间的思维。下面,我们看下新的日期和时间API的用法:

public class TestNewDate {public static void main(String[] args) {// 获取表示当前时刻的LocalDateTime对象LocalDateTime date1 = LocalDateTime.now(ZoneId.systemDefault());// 获取指定时间的LocalDateTime对象LocalDateTime date2 = LocalDateTime.of(LocalDate.of(2020, 2, 10),LocalTime.of(0, 0, 0));// 进行是时间比较System.out.println("date1 is after date2: " + date1.isAfter(date2));// 进行时间的加减法,如获得昨天的这个时刻:LocalDateTime date3 = date1.minus(Period.ofDays(1));// 对时间进行格式化输出DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");System.out.println("date3: " + df.format(date3));}
}
/* Output:
date1 is after date2: false
date3: 2020-02-07 23:56:51
*/

实现同样的功能,JDK 1.8版本的日期和时间API明显更加简洁,而且代码的逻辑更加符合人的思维,可读性更好(比如使用.now()函数创建当前时刻的LocalDateTime对象,代码阅读起来就跟人类的自然语言一样)。由此可见,设计出一个高质量的领域模型对于软件系统是多么的重要。

在这个例子中,JDK并没有显式地使用DDD提供的战术建模手段对日期和时间API进行设计,但是从JDK 1.1到JDK 1.8版本中的变化,其中就蕴含着DDD最核心的内容:设计出更符合业务规则和人类思维的领域模型。从这个例子中我们也能看出,DDD并没有传说中的那么神秘,也未必一定要运用在复杂的系统,即使是一个简单的日期和时间API,其中也可以看到它的身影。

如果让你对JDK 1.1的日期和时间API进行优化,相信很多人都设计不出像JDK 1.8版本的这样优秀的API,不管在经验,还是在方法上我们都欠点火候。简单的API如此,更别说设计复杂的大型系统了。这时,我们需要一些具体的建模方法来指导设计。

DDD的建模方法

DDD主要提出了两种建模方法来帮助我们设计出高质量的领域模型:战略建模和战术建模。

战略建模根据领域知识对系统进行限界上下文和子域的划分,战术建模具体为每个限界上下文设计出领域模型。而这两者中又内涵很多知识点,光看下面的这张DDD的概念图,你可能会觉得DDD太过复杂了。

18c450de9c13448dc619c723a8fa96c6.png

确实,DDD的学习曲线比较陡,特别是第一次看Eric Evans所著的《领域驱动设计——软件核心复杂性应对之道》时,会有种不知所云的感觉。再看Vaughn Vernon所著的《实现领域驱动设计》可能会好点了,但是里面提到的各种概念还是没能很清晰地理解。所谓“实践出真知”,只有通过不断地实践,才能学习到DDD的精髓,体会到它的魔力。下一篇文章,我们将开始通过实践一个简单的业务功能着手介绍DDD的各种理论知识。

http://weixin.qq.com/r/Tx2zqxDE602UrVTI90hd (二维码自动识别)

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

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

相关文章

linux 进程通信 消息队列

详解linux进程间通信-消息队列 前言:前面讨论了信号、管道的进程间通信方式,接下来将讨论消息队列。 一、系统V IPC 三种系统V IPC:消息队列、信号量以及共享内存(共享存储器)之间有很多相似之处。 每个内核中的 I P …

wx.checkjsapi是写在config里面吗_用Python写一个程序,解密游戏内抽奖的秘密

前言本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。作者: 极客挖掘机PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http://t.cn/A6Zvjdun分析需求我们先整理下思…

Dev C++安装第三方库boost

Dev_C安装第三方库boost 安装步骤 准备工作下载boost库,下载地址https://sourceforge.net/projects/boost/1. 设置GCC的环境变量PATH 设置环境变量path,在其中加上DEV-C编译器的路径(gcc.exe所在路径),如C:\Program Files (x86)…

bash的一些小技巧

1、从输入读入变量 eg:read -ep "input yes or no: " flag 用e选项表示编辑,可以使用backspace删除 2、数组 a、索引数组 declare -a arr(var1 var2 var3) 用空格分割,如果直接访问变量$arr, 则获取的是数组的第一个元素&#xff0…

golang switch_为什么程序员都不喜欢使用 switch ,而是大量的 if……else if ?

点击上方“我要学编程”,选择“置顶/星标公众号”福利干货,第一时间送达!来自 | C语言Plus请用5秒钟的时间查看下面的代码是否存在bug。OK,熟练的程序猿应该已经发现Bug所在了,在第13行下面我没有添加关键字break; 这就…

RabbitMQ 安装与简单使用

在企业应用系统领域,会面对不同系统之间的通信、集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要。其次,系统中一般会有很多对实时性要求不高的但是执行起来比较较耗时的地方,比如发送短信&#…

windows svn

windows svn 1.1Svn和VisualSvn介绍 VisualSvn Server2.5.6(版本控制服务器)免费开源软件 是基于Windows平台上的Subversion服务器,它是免费的 官方下载: http://www.visualsvn.com/files/VisualSVN-Server-2.5.6.msi TortoiseSvn…

docker-compose下载慢_编写Docker Compose时要注意的五大常见错误

在构建容器化的应用时,开发人员往往需要某种方法来引导启动目标容器,以对其进行代码级别的测试。尽管业界有许多方法可以实现该目的,但Docker Compose是目前最受欢迎的一种方法。它能够让如下两个方面变得容易实现:指定在开发过程…

frame越过另一个frame_拥抱swoole(三)之用php实现一个混合服务器

混合服务器,就是可以同时支持http,websocket,tcp等的服务器,用swoole就是这么简单,分分钟,就可以愉快地搞物联网开发了,啥都支持,我采用官方的例子,创建一个混合服务器&a…

Hibernate学习系列————注解一对多单向实例

2019独角兽企业重金招聘Python工程师标准>>> 开发环境:MysqlEclipse 一对多单向的列子原理:一个班级,多个学生,学生端为多的一端,他们拥有一个外键指向相同的班级。 项目结构 需要的jar包 hibernate.cfg.xm…

sudo apt-get nmap 报错锁占用

在Ubuntu中用apt-get命令安装软件是出现如下错误: 网上搜了一下原因,说是有另外一个程序在运行,导致锁不可用,原因可能是赏析运行更新或安装没有正常完成。这是因为上次更新或者安装没有正常完成。 网上的两种解决方法&#xff1…

python逐行读取txt写入excel_用python从符合一定格式的txt文档中逐行读取数据并按一定规则写入excel(openpyxl支持Excel 2007 .xlsx格式)...

前几天接到一个任务,从gerrit上通过ssh命令获取一些commit相关的数据到文本文档中,随后将这些数据存入Excel中。数据格式如下图所示观察上图可知,存在文本文档中的数据符合一定的格式,通过python读取、正则表达式处理并写入Excel文…

Extjs 之 initComponent 和 constructor的区别(转)

在创建自定义类时,先构造(constructor)后初始化(initComponent)。如:(在旧的Extjs 版本中使用 Ext.extend 实现扩展) Ext.define(Btn,{ extend:Ext.button.Button, init…

hive遍历_从Hive中的stored as file_foramt看hive调优

一、行式数据库和列式数据库的对比1、存储比较行式数据库存储在hdfs上式按行进行存储的,一个block存储一或多行数据。而列式数据库在hdfs上则是按照列进行存储,一个block可能有一列或多列数据。2、压缩比较对于行式数据库,必然按行压缩&#…

帮助孩子学会感恩_页数204_出版日期2015.03_完整版PDF电子书下载

帮助孩子学会感恩_页数204_出版日期2015.03_完整版PDF电子书下载 带索引书签目录高清版_13813212 下载链接http://pan.baidu.com/s/1geEmUeZ 【作 者】(英)蒂姆惠特尼(TimWhitney)著【丛书名】陪孩子成长系列丛书【形态项】 204 …

xwpftablecell设置字体样式_HTML的文字样式

font 属性可以用来作为 font-style, font-variant, font-weight, font-size, line-height 和 font-family 属性的简写,或将元素的字体设置为系统字体。字体修改font-family 属性:设置HTML页面中的字体font-size 属性:设置字体大小font-weight…

15-CSS基础-浮动流

浮动 网页的布局方式 什么是网页的布局方式? 网页的布局方式其实就是指浏览器是如何对网页中的元素进行排版的 标准流(文档流/普通流)排版方式 其实浏览器默认的排版方式就是标准流的排版方式在CSS中将元素分为三类, 分别是块级元素/行内元素/行内块级元素在标准流中有两种排版…

git-- 使用

git 使用时两个人冲突: Resolve conflicts

高内聚低耦合通俗理解_抱歉,请不要把“业务逻辑层”理解为“业务中台”

在IAS2019中台架构峰会上,我曾与一位年轻帅气的技术小伙来了一番有趣的对话。因为和朋友有约,所以我在现场互动结束之后,就急匆匆地跟其他嘉宾打了声招呼,抱着笔记本冲出了会场。但没想到刚到电梯口,却被一位帅小伙迎面…

org-mode入门教程

org-mode 入门教程By Z.H. Fu切问录 www.fuzihao.orgorg-mode 入门教程 org-mode是Emacs提供的一个强大的编辑模式,可以用于做会议笔记以及制作各种待办事项(GDT)。其语法类似于Markdown但是提供了比Markdown更多的操作,再加上Ema…