产品代码都给你看了,可别再说不会DDD(三):战略设计
这是一个讲解DDD落地的文章系列,作者是《实现领域驱动设计》的译者滕云。本文章系列以一个真实的并已成功上线的软件项目——码如云(https://www.mryqr.com)为例,系统性地讲解DDD在落地实施过程中的各种典型实践,以及在面临实际业务场景时的诸多取舍。

本系列包含以下文章:
案例项目介绍
既然DDD是“领域”驱动,那么我们便不能抛开业务而只讲技术,为此让我们先从业务上了解一下贯穿本文章系列的案例项目 —— 码如云(不是马云,也不是码云)。如你已经在本系列的其他文章中了解过该案例,可跳过。
码如云是一个基于二维码的一物一码管理平台,可以为每一件“物品”生成一个二维码,并以该二维码为入口展开对“物品”的相关操作,典型的应用场景包括固定资产管理、设备巡检以及物品标签等。
在使用码如云时,首先需要创建一个应用(App),一个应用包含了多个页面(Page),也可称为表单,一个页面又可以包含多个控件(Control),比如单选框控件。应用创建好后,可在应用下创建多个实例(QR)用于表示被管理的对象(比如机器设备)。每个实例均对应一个二维码,手机扫码便可对实例进行相应操作,比如查看实例相关信息或者填写页面表单等,对表单的一次填写称为提交(Submission);更多概念请参考码如云术语。
在技术上,码如云是一个无代码平台,包含了表单引擎、审批流程和数据报表等多个功能模块。码如云全程采用DDD完成开发,其后端技术栈主要有Java、Spring Boot和MongoDB等。
码如云的源代码是开源的,可以通过以下方式访问:
战略设计
在上一篇DDD概念大白话中,我们提出了一个观点:DDD的战略设计只在解决一个问题,即软件的模块化划分的问题。在本文中,我们将对此做出详细的解释。
不过,首先让我们来看看DDD的战略设计原本包含哪些内容。战略设计中包含领域、子域、通用语言和限界上下文等概念。领域(Domain)表示一个行业中所发生的一切业务;子域(Subdomain)则表示领域中细分之后的子业务,是比领域更小的概念,子域又可细分为核心子域(Core Domain)、支撑子域(Supporting Domain)和通用子域(Generic Domain);通用语言(Ubiquitous Language)表示在领域中所有人员都使用一套相同的语言进行沟通交流;限界上下文(Bounded Context)则表示由通用语言所形成的上下文边界。读到这里,你是不是感觉好像什么都说了,又感觉什么都没说?
事实上不难看出,无论是从领域到子域,还是从通用语言到限界上下文,其中都体现了一种“分”的思想,这种思想也正是整个计算机科学中的一种基本思想——分治法(devide and conquer)。作为顶层设计的DDD战略设计来讲,这种“分”的思想的落地不正是我们在软件架构图中所看到的那些方块么?不正是软件的模块化划分么?
有了以上认识,再让我们来重新审视一下战略设计中的各种概念。软件中有些模块是业务的核心,对应着DDD中的“核心域”的概念,比如电商系统中的订单模块;有些模块对核心模块起支撑作用,对应DDD中“支撑域”,比如电商系统中的积分模块;而有些模块是通用性质的,对应着DDD中的“通用域”,比如登录管理模块。限界上下文可以看做是子域落地后的概念,因此通常与子域存在一一对应的关系,也即一个限界上下文表示一个模块。限界上下文可以这么理解:在DDD中,允许在不同的模块中存在相同名称的对象,但是它们在各自的上下文中所表示的含义是不同的,这也是“限界”一词的由来。举个例子,在电商系统中,存在交易模块和物流模块,它们都包含“订单(Order)”对象,但是交易模块中的订单和物流模块中的订单所承载的业务含义是不一样的,在交易模块中我们更专注订单的价格、数量和折扣等,而在物流模块中我们则更关注订单的重量、体积和物流状态等。

说DDD的战略设计只是模块化划分并不是要贬低战略设计的意思,事实上恰恰相反,战略设计很重要。DDD的开山鼻祖Eric Evans曾经说,如果让他重新撰写《领域驱动设计》那本书,他会将原书中的很大部分全部撕掉,然后用于撰写与限界上下文相关的内容,从此也可见战略设计的重要性。但是,我们希望做的是让读者认清其中的本质,毕竟DDD本身是一种实践性很强的学问,我们对DDD的认识不应该停留在对概念的咬文爵字上,而是真正能够产出高质量的软件。
事实上,软件的模块化划分是一个非常古老的概念,它伴随着软件的诞生而诞生,其萌芽至少可以追溯到世界上第一台通用电子计算机ENIAC的发明者之一约翰·皮斯普·埃克特(J. Presper Eckert)在一篇研究穿孔纸带的论文中所提到的“Decomposition(分解)”。

后来,软件的模块化经道格拉斯·麦克罗伊(Douglas McIlroy)和布莱德·考克斯(Brad Cox,Objective-C发明人)等人得到了进一步发展。如果我们再将眼光放开阔一些,你会发现模块化的思想存在于各个行业中,比如船舶、桥梁、建筑以及航空等领域。

因此,模块化对于接受了现代工业文明洗礼的我们来说,并不是一个陌生的词汇。然而,难点并不在于如何定义模块,而在于如何划分模块。在DDD中,这是一个见仁见智众说纷纭的话题,为此,让我们从一个小故事展开。
一个2岁的幼儿,从来没有看到人的头像简易画(下图中左边的图片),但是当你问他那是什么的时候,他可能会说“人人”。这是为什么?

幼儿能够辨认出他从来没有看到过的东西,是因为他拥有两种能力:经验和抽象。他虽然没有看到过人头像简易画,但是他之前一定看到过真实的人,此所谓经验,也即我们过去所经历的事情;而他能够将人像简易画和真实的人对等起来,则是因为人类与身俱来的抽象能力。此二者,恰恰是我们划分软件模块所需要的东西,并且人人皆有。因此,你并不需要一套专门的学问来指导你完成DDD的战略设计,你需要的依然是那些在日常工作生活中我们始终在使用着的技能。
但是,经验有多有少,抽象有深有浅,导致不同的人所划分出来的模块形态也不一样。为了做好DDD战略设计,你需要有充足的经验以及对业务的深入了解。那么,经验到底到底多少算多呢?5年工作经验够不够?10年又够不够?这种按照年限来区分经验多寡的方式是不合适的,一个10年工作经验的架构师,他可能在这10年内一直在重复性地做着一件事情,而一个3年工作经验的程序员,却可能已经经历过很多项目、技术以及行业。因此,经验是根据你在自己所处的行业中所耕耘的深度和广度来计算的,而非时间。
你可能会认为经验这个东西太不可名状了,无法提炼出一套有据可循理论框架出来,的确没有。然而,这正是软件被称之为艺术的原因,它让每个人都有属于其自己的发挥空间,况且还有大哲学家和大科学家为你背书,你还那么不自信到要去追求一个咨询师没把你教会而你自己也没学会的所谓的理论框架吗?坦诚点,自信点,自豪地告诉别人:“我通过自己对业务的深入了解,外加自己的从业经验和抽象能力,搞定了DDD的战略设计!”

让我们来看个例子吧,搜索功能是多数应用网站都有的功能,在一些应用中,搜索可以简单到只是做个正则匹配的小功能点,此时的搜索固然称不上一个模块,而在另一些应用中,比如大型电商网站,搜索包含了多条件多方式查询等众多内容,其后台的软件架构和技术栈甚至都是专门设计的,此时的搜索功能你哪怕找一个毕业生来设计估计结果都是一个独立模块。这里,从功能点到功能模块的变化过程中,没有什么量化工具和理论框架可言,说得直白点,这就是架构师的一个主观认知而已。但是,这个认知却是重要的,因为它体现了架构师对于一个问题的抽象能力,以及对于软件边界的识别能力。什么是架构呢,一种解释是软件架构是项目中的资深程序员们对某个问题所达成的统一认识而已。
理论框架虽然没有,但是指导性的原则还是有的,以下原则是程序员们耳熟能详的编程原则,将其用在模块化划分上依然成立:
- 高内聚,低耦合原则
- 关注点分离原则
- 单一职责原则
说到DDD,我们可能不得不说一下微服务,因为一般的理解是DDD因为微服务的兴起而重新被业界重视。事实上,DDD和微服务的关系被牵强式地放大了,DDD之于微服务,无外乎“DDD的限界上下文可以用于指导微服务的划分”,然而,在我们把限界上下文理解为模块后,这种说法也就不值得再成为一个单独的命题了。DDD的意义在于“DDD之于软件”,而不是“DDD之于微服务”。
在码如云,我们采用了单体架构而非微服务,但这并不影响我们划分限界上下文(模块),我们通过Java分包的方式来划分模块。

码如云的模块关系并不复杂,仅包含3个顶层模块,一个是核心上下文(模块),其中包含各种核心的业务实体,比如应用和实例等,每个业务实体均被建模为一个聚合根;第二个是后台管理上下文(模块),用于码如云的后台运营,包含客户关系、投诉管理和订单管理等;第三个是集成上下文(模块),用于处理与第三方的API集成。在前文中我们提到,登录功能可以被看做是通用子域而建模为一个独立的模块,但是在码如云中我们并未这么划分,而是将登录功能消化在了核心上下文中,因为其粒度尚未大到需要独立为一个模块的程度。
总结
很多简单的东西被人为的复杂化了,当资深人士们还在高谈阔论微服务和SOA的区别时,Robert C. Martin(Bob大叔)站出来说,这俩就是一个东西。当下的DDD同样也在遭受着“被复杂化”的境遇,软件(至少企业级应用软件)向来的实践性是非常强的,结果从业者们自己把自己搞不会了,实则不应该呀!在本文中,我们说DDD的战略设计只是在解决软件的模块化划分的问题,并不是要贬低战略设计,而是希望读者看到战略设计的本质,进而从DDD中得到实实在在的好处,也推动这个行业可以健康地,不要那么浮夸式地发展。
产品代码都给你看了,可别再说不会DDD(三):战略设计的更多相关文章
- 瞧一瞧,看一看呐,用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!!
瞧一瞧,看一看呐用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!! 现在要写的呢就是,用MVC和EF弄出一个CRUD四个页面和一个列表页面的一个快速DEMO,当然是在不 ...
- iOS开发UI篇—从代码的逐步优化看MVC
iOS开发UI篇—从代码的逐步优化看MVC 一.要求 要求完成下面一个小的应用程序. 二.一步步对代码进行优化 注意:在开发过程中,优化的过程是一步一步进行的.(如果一个人要吃五个包子才能吃饱,那么他 ...
- 每一行代码都有记录—如何用git一步步探索项目的历史
每一行代码都有一块被隐藏了的文档信息. 下面的代码片段不管是谁写的,其第4行因为某些原因要访问一个DOM结点的clientLeft属性,但却对结果不作任何处理.这十分的莫名其妙,你能告诉我他们为什么要 ...
- Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!
本文原题“从实践角度重新理解BIO和NIO”,原文由Object分享,为了更好的内容表现力,收录时有改动. 1.引言 这段时间自己在看一些Java中BIO和NIO之类的东西,也看了很多博客,发现各种关 ...
- 圣诞节,把网站所有的js代码都压缩成圣诞树吧。
本文分两章节,分别讲解如何使用js2image这个库生成可以运行的圣诞树代码 和 js2image的原理. github地址:https://github.com/xinyu198736/js2ima ...
- delphi 动态绑定代码都某个控件
delphi 动态绑定代码都某个控件 http://docwiki.embarcadero.com/CodeExamples/Berlin/en/Rtti.TRttiType_(Delphi)Butt ...
- 产品经理都知道MVP,但是它可能不再是产品研发最好的模型了
产品经理都知道MVP,但是它可能不再是产品研发最好的模型了 孟小白Aspire • 2017-09-01 • 汽车交通 要简单.讨喜.完整,不要最小可行性产品.这对创业公司的第一个产品来说很重要. M ...
- appium+python解决每次运行代码都提示安装Unlock以及AppiumSetting的问题
appium+python解决每次运行代码都提示安装Unlock以及AppiumSetting的问题(部分安卓机型) 1.修改appium-android-driver\lib下的android-he ...
- 使用Junit测试一个 spring静态工厂实例化bean 的例子,所有代码都没有问题,但是出现java.lang.IllegalArgumentException异常
使用Junit测试一个spring静态工厂实例化bean的例子,所有代码都没有问题,但是出现 java.lang.IllegalArgumentException 异常, 如下图所示: 开始以为是代码 ...
- 轻松解决 CSS 代码都在一行的问题
前言 最近在做博客园的界面美化,用的是博客园[guangzan]的开源项目,配置超级简单,只需要复制粘贴代码就好啦. 但在粘贴 CSS 代码时遇到一个问题,那就是所有代码都挤在了一行,没有一点排板的样 ...
随机推荐
- 2022-02-25:k8s安装zookeeper,yaml如何写?找份北京的golang后端工作,35岁,有人收我吗?
2022-02-25:k8s安装zookeeper,yaml如何写?找份北京的golang后端工作,35岁,有人收我吗? 答案2022-02-25: yaml如下: apiVersion: apps/ ...
- 2022-02-07:k8s安装mysql,yaml如何写?(非面试题)
2022-02-07:k8s安装mysql,yaml如何写?(非面试题) 答案2022-02-07: yaml如下: apiVersion: apps/v1 kind: Deployment meta ...
- 从零玩转之JPOM自动化部署本地构建 + SSH 发布 java 项目
简而轻的低侵入式在线构建.自动部署.日常运维.项目监控软件 一键部署Jpom 本文主要介绍: 如何从零开始使用一键安装的方式安装 Jpom 服务端+插件端配置 本文中服务端和插件端是安装在同一个服务器 ...
- Python 日期和时间函数使用指南
在本教程中,我们将介绍 python 的 datetime 模块以及如何使用它来处理日期.时间,以及日期时间的格式化处理.它包含各种实用示例,可帮助您通过 python 函数更加快捷高效进行日期和时间 ...
- uniapp 全局背景音乐播放+暂停(跳转页面不暂停)
最近需要一个功能 是在h5中播放小游戏的背景音乐,但是跳转界面之后音乐不暂停,就是跳转多个页面之后,音乐依然在播放,在游戏界面会有设置的静音的按钮,可以开启音乐和关闭音乐. 单独建了一个music.j ...
- Eclipse的Console如何实现中文输出(Eclipse Display Chinese)
最近遇到Eclipse的Console中文输出乱码的问题,现象如下: 在网上找到一些方法,一般均不好用,直到找到"如何在Eclipse控制台中显示汉字",链接如下 https:// ...
- Redis系列16:聊聊布隆过滤器(原理篇)
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...
- SQL专家云快速解决阻塞
背景 当数据库突然产生严重阻塞时,运维人员要快速找到阻塞的源头并处理,让业务快速恢复.但是大多数运维人员只掌握了sp_who2.sp_lock等简单的语句,存在以下不足: 找不到真正的源头,过程中会误 ...
- C++容器(vector、deque、list、map)
(1) vector:将元素置于一个动态数组中,可以随机存储元素(也就是用索引直接存取). 数组尾部添加或删除元素非常迅速.但在中部或头部就比较费时. *代码演示:* 取: at在下标越界时会抛出异常 ...
- React后台管理系统10 菜单数据的整理、以及其余路径的配置、刷新时默认当前选中样式
对菜单进行数据整理 import { DesktopOutlined, FileOutlined, PieChartOutlined, TeamOutlined, UserOutlined, } fr ...