第2章 战略设计

第2章 战略设计

  • 2-1 智慧零售案例项目介绍
  • 2-2 建模和设计的整体流程
  • 2-3 如何描述模型?从用户故事到通用语言
  • 2-4 SmartRM系统整体战略设计
  • 2-5 SmartRM通用语言文档
  • 2-6 分解问题:领域划分和子域
  • 2-7 确定系统最核心的部分:核心域和精炼
  • 2-8 分解模型:限界上下文
  • 2-9 多个上下文之间如何协作?上下文映射和防腐层
  • 2-10 剥离领域模型与技术实现:建立分层架构

2-1 智慧零售案例项目介绍

大家好,这一节重点介绍我们的案例项目DDD是一门实践学科,因此案例呢就显得尤为重要。
我们的课程也会用一个案例来贯穿,始终。
通过这个案例来学习应用DDD进行系统建模和设计的完整过程,从需求分析到战略设计,再到战术设计,一直到编码阶段都会有详细的涉及,从截标题就能知道这是一个智慧零售相关的项目。
那么为什么我们会选择这样一个行业呢?
首先,零售行业规模巨大,二零二零年的市场规模达到了三十九点二万亿,在GDP中呢占比是非常高的,差不多是在百分之四十左右。
所以这是一个在经济中有支柱地位的行业,而其中线上零售的实物部分呢,也就是不包含虚拟商品的这部分呢占比大约在百分之二十四点九。
所谓的虚拟商品就像包括QQ会员呐游戏点卡呀这些,而线下零售仍然占了超过百分之七十左右。
所以大家可以看到电商经过了这么多年的发展,并没有像某些大佬说的那样,全面超越传统的线下零售到这个阶段呢就早早陷入僵局了。
而某些大厂呢,其实现在已经开始面临流量枯竭的问题了。
那么为什么会这样呢?
这是因为零售行业本质上并不是一门流量的生意而流量。
也就是用户相关的这部分只是其中一小部分,整个零售概括起来需要解决的问题,包括三个方面的问题,也就是人货场三方面的问题。
所谓人呢指的是用户货呢,指的是商品和场啊,是指交易完成的场所,包括各种形式的卖场啊商超啊平台啊等等。
那其中呢人和厂是和用户体验相关的。
而货和厂呢是和供应链相关的,而供应链才是零售中最重要的问题。
道理很简单,大家买东西最主要看的是什么呢?
最主要看的还是性价比,对不对?
零售企业的供应链能力才能决定性价比。
电商最擅长的是人最多,还涉及到厂。
也就是说,电商最擅长的还是用户体验。
所以电商占零售大盘的比例想超过百分之五十是非常困难的。
所以京东从来不说自己是一家电商公司,而称自己为供应链公司,而作为电商巨头的阿里呢也提出来新零售这个概念。
而作为腾讯呢则提出来自己的智慧零售这个概念。
二者虽然有差别,但是想做的事情是类似的,也就是用云大数据AI和物联网相关的技术去影响零售中的人货和厂,从而对零售的供应链和用户体验进行优化。
他们都希望通过智慧零售或者新零售的概念,把自己的价值带到零售行业完整的价值链,而不仅仅限于用户一侧,而传统的零售公司呢,也希望借助技术来优化自己的供应链的质量和效率。
同时呢优化用户体验,把用户粘在自己的平台上,而不用依赖于传统的电商渠道。
所以从这点上来说呢,传统的零售公司和新兴的互联网技术公司,对于智慧零售和新零售的诉求是一拍即合的。
所以呢智慧零售和新零售就成了零售行业后面发展的一个大势所趋。
那么介绍完了行业背景,接下来让我们重点介绍一下我们的业务背景。
大家知道无人零售是新零售和智慧零售中一个重要的方向。
那我们的案例呢,也是面向无人零售相关领域的,我们要开发的是一个面向自动售卖机的这么一个零售saas系统。
那其中呢既包括了面向用户的交易部分,也包括了面向企业中的运营人员的供应链管理部分。
这个系统呢连接了用户零售业务运营人员和企业的内部系统,它既要负责管理和操控自动售卖机,并且提供交易系统,让用户在售卖机上完成购物,又要负责帮助运营人员完成商品的选品和库存管理,实现运营利润的最大化。
同时呢又要连接客户的内部系统,从而充分的利用客户现有的内部资源和流程,客户的运营人员可以在这个系统上决定在哪个地方投放售卖机,而商务和安装人员呢会通过企业ERP系统收到投放申请,最终由安装人员完成。
售卖机的安装安装好之后呢,运营人员会在我们的sars上针对已上线的设备选择要进行售卖的商品,并采用一定的策略,使得商品能够及时得到补货,始终处于一个可以购买的状态。
这些策略会被我们的系统转化成配送申请,发送给企业内部的配送系统,最终由企业的配送人员完成商品的配送。
大家可以看到,在这个领域中,客户企业现有的内部系统也会深度参与,其中包括企业内部已有的erpp系统商品台台送中台等等,可能不同企业内部系统的架构会有所区别。
但是大体上一个成熟的零售公司,会有这几部分的系统支撑。
其实现在大多数的业务系统都会面临这么一个情况,一方面会有自己的创新部分,一方面呢又会与外部系统和历史系统进行交互,充分利用各种资源。
所以我们的案例也会模拟这么一种情况。
我们会来看看滴滴滴是怎么应对这种情况的。
假设我们面向的客户就是这么一个比较成熟的零售企业。
这个企业呢想引入自动售卖机作为新的业务形态。
而我们的系统呢实现的就是基于自动售卖机的零售系统,包括交易啊和运营。
我们称这个系统为smartIM也就是smartretailmachine的简写。
我们假设我们所在的公司是一个主要面向零售企业客户的sars公司名字叫做a公司。
而smartRM的研发团队呢,包括产品团队和研发团队,产品团队由一位资深产品经理带队下面有几个产品策划啊,研发团队是由一位架构师带队。
这位架构师,我们假设就是你下面有十多个,一开发,同学里面有三到四位的能力和经验比较强的自深开发。
而我们的第一位客户呢是零售行业的某头部商家叫做b集团。
b集团在全国各地有大量的商超和便利店,有成熟的内部系统和供应链。
就像我们前面所说的,b集团内部有成熟的ERP系统,也有自己的商品中台商品中台里呢能获取他们现在所有的可以售卖的商品数据,并且提供检索接口,他们也有自己成熟的配送系统,对外呢以配送中台的形式提供服务,会有api提供给我们。
不过基于售卖机的零售业务,对b集团来说是一个新业务。
他们希望借助smartRM来完成新业务的一个搭建。
那么我们什么么会选择这样一个例例,其实主要考虑虑的是三点。
首先这个案例复杂度足够可以覆盖d滴滴大部分的知识点,体现他们的价值。
但是同时呢我们也会对业务进行一定程度的简化,将业务的复杂性控制在一个可以接受的水平,从而力求不给大家的学习带来太重的业务知识的负担。
第一章我们提过在敏捷软件开发里面,我们可以分多个冲刺来实现整个软件系统。
那么我们的案例要做的就是第一个冲刺的建模和设计工作。
第一个冲刺里面呢,我们只做最核心的部分,而可以去简化那些不怎么核心的部分和周边系统,但是呢也要使软件能够运行起来。
那么这个案例的第二点呢,就是智慧零售这个方向的市场庞大未来可期,而且场景接近日常生活。
第三点呢就是这个系统会有大数据分析的需求,可以结合大数据分析场景OK那么让我们来做一个简单的总结。
在这一节呢,我们把案例项目的行业背景业务团队乃至客户的背景都做了一个详细的了解,并且明白了我们为什么要用这么一个案例来带大家学习。
这里我想强调一下,滴滴滴是一门实践学科,只有通过案例。
我们才能充分理解里面的概念和方法,从而达到学以致用的目的,可能会有一些同学对了解案例中的业务知识有抵触或者畏惧的情绪。
但是这里我想说的是DDA它的目的就是控制软件系统中的业务复杂性。
所以完整的理解案例中的业务是非常有必要的。
希望大家在后面的学习中呢能够理解案例的业务,重视案例的学习身临其境的跟着我一起来用滴滴滴完成整个案例的建模和设计过程。
ok本节的内容就是这些,谢谢大家。
我们下节再见。

2-2 建模和设计的整体流程

大家好,这节课呢我们要学习的是建模和设计的整体流程,目的是对我们要学习的滴滴滴以及它在项目中的应用方法,先有一个全局性的认识。
在这一节呢,我们会了解到软件系统从需求到最终的技术方案,要经过哪些环节,各个环节都要达成什么样的目的,以及大体上做什么,也就是各个环节的目标和概要,最后呢也会列举常用的一些建模方法。
那么让我们植入主题第一章我们已经学习过所谓的建模和设计,就是把领域知识和需求转换成代码的过程。
在DDT中呢,我们可以把这个过程划分为这四个阶段挖掘领域故事,建立通用语言战略设计和战术设计。
从上面这个图,我们可以看到这四个阶段虽然开始的时间有先后,但并不是上一个阶段结束,下一个阶段才开始,而是重叠进行的。
对用户故事的挖掘和建立通用语言是最先开始的,也可能会贯穿整个建模设计的过程。
同样,战术设计的开始也并不意味着战略设计的结束。
在战术设计的过程中,我们有可能会发现战略设计的不合理,从而回过头来对战略设计进行优化。
同时,在战略设计和战术设计中,我们也会不断的对通用语言进行丰富,甚至发现业务流程中的优化点,从而回过头来重新研究用户故事。
所以这里我们也要提到ericevans曾经提出的建模蜗牛这个概念,什么是建模蜗牛呢?
就是指两种活动交替完善,最终得到一个合理的模型。
其实在建模过程中,这种蜗牛有不少刚才我们提到的战略设计和战术设计的交替完善,就是其中一种。
举个例子说,我们在战略设计中会将模型分解成线阶上下文。
但是随着建模的深入呢,更多的实体被发现,有可能我们会发现有必要分割出新的上下文。
那么对战略设计进行修改,以及对通用语言进行补充和完善,都是没有问题的。
而且在这种情况下呢,也是必须要做的,再举一个更细腻度的例子。
在战略设计的内部,我们要先对领域进行划分,然后再寻找线阶上下文。
理想情况下呢,子领域和县界上下文是一一对应的,但是有可能在寻找线接上下文的过程中,我们会发现一个子领域下划分出了两个线接上下文。
那么这就说明之前的领域划分是存在问题的,需要进行调整。
而调整之后的领域划分呢也能更好的指导线阶上下文的划分,从而形成一个良性循环。
这就是另外一个蜗牛。
所以说建模设计过程并不是一个那么规整的过程,它是以形成最优模型为目标的一个不断优化的过程。
那么建模设计过程需要谁来参与呢?
团队里一般会有三种角色领域专家研发团队以及产品团队,其中领域专家是非常重要的角色领域。
专家是指在某个领域有深度经验的专业人士。
当我们要做一个外卖系统的时候,外卖员就是领域专家。
当我们要做一个医疗系统的时候,医生就是领域专家。
但是在现实中呢,很多情况是产品经理来担当领域专家的角色,这也是可以的。
但是必要的时候需要邀请相关行业的业内人士参与讨论,整个建模和设计流程的第一步,要从诱惑故事开始。
在敏捷软件开发中呢,用户故事是一种对软件系统特性的非正式的描述是从终端用户的角度,对问题的描绘。
所以我们说它是对问题空间的描绘,很多团队在做产品的时候,一上来产品经理先出一个无比细致的设计文稿,各种流程图啊,界面一应俱全,开发同学也很喜欢用写需求文档不细致。
这一点来怼产品就好像是一种政治正确。
其实软件产品开发并不是按部就班的搬砖,再牛的产品经理,他也做不到,一开始就确定产品的最终方案。
如果他在各方压力,尤其是来自开发一侧的压力下,真的尝试去这么去做。
一开始就把产品从功能到界面全部定稿,并且保证永不再改。
那么最后出来的产品很可能是没有生命力的产品。
好的产品它一定是经过充分的讨论,反复修改,打磨得到的。
当然经过讨论之后得到的最终方案,他还是要写清楚的那且好的产品文档呢也可以作为通用语言的文档。
所以基于这一点呢,敏捷方法提倡在早期也用用户故事来对需求进行恰到好处的描述,而不会太过细致。
用户故事更多的是对问题的描述,而不是解决方案。
通过从问题出发,通过整个团队的讨论论证,包括领域专家研发产品经理之间的充分讨论,不断递进,最终接近答案,也就是最合理的产品方案,用户故事一般类似右边这种描述方式。
作为什么人,我希望做什么,从而达到一个什么样的目的?
比方说在自动售卖机上,通过手机扫码支付购物,这个用户故事就可以这样进行描述。
作为用户,我希望通过手机支付在自动售卖机上购买商品,从而能够更方便快速的购物,通过文字对他进行描述之后呢,还要把团队内部相关人员聚在一起进行讨论。
比如售卖机扫码支付购物,这个就可以把相关的产品经理研发必要的时候,还可以包括售卖机的运维人员或者技术知识人员聚在一起进行讨论,一边讨论,一边画图,类似于右边这个图也可以是更简单的压图。
通过这种图呢就可以快速的把讨论的结果固化下来。
右边这个图是对电影购票的这个场景的描述。
从里面我们可以看到一些句子。
比如第一步,顾客向售票员请求预留座位。
第二步,购票员去票务系统上从放映计划,让查找空闲座位。
第三步,售票员向顾客推荐座位等等。
可以看到,通过这种过程,我们慢慢从问题空间进入方法空间了,也就是逐步发现顾客和售票员。
可以怎么样通过票务系统来解决买票这个问题了。
通过讨论画这个图的过程呢又叫做domainstory,telling在后面讲通用语言的时候,我们还会进行详细介绍。
这就是我们挖掘用户故事的过程。
除了domainstorytelling也可以用其他的一些魔方法,包括像事件风暴来对用户故事进行挖掘。
接下来的一步是建立通用语言。
通用语言是DDD里面非常重要的概念。
所谓通用语言,就是指在讨论和定义模型时,团队使用的同一种语言。
为什么要有通用语言呢?
答案很明显,因为首先领域专家脑子里的知识需要传达给软件开发人员,否则没法开发,对不对?
其次,建模得到的模型需要进行描述和记录,而且对模型的描述呢,领域专家也要能够看懂,以便进行反馈。
所以我们要尽快的建立通用语言。
最后呢通用语言也会体现在代码里面代码中的类名啊属性名啊方法名都要尽可能的使用通用语言中出现过的词汇进行命名。
其实在对用户故事进行挖掘的时候,我们就已经是在建立通用语言了。
在讨论的时候,大部分时间是领域专家在讲解其他人要做的是理解领域专家提到的各种概念,及时提问,同时把他们固化下来。
打个比方说,如果我们在讨论自动售卖机团队里面有人说售卖机,那有的人说贩卖机,那么你可以提出来,我们以后统一称它为售卖机,甚至把出现过的关键词汇提炼成一个表格。
左边这个图是我们对案例中货到售卖机扫码支付购物这个用户故事进行挖掘得到的图。
这里大家有个印象就可以了,后面我们还会详细讲解。
右边我们把这个图里出现的名词提炼出来,放到了表格里中英文都固定下来。
那在后面的战略设计和战术设计的过程中呢,我们还可以不断的对通用语言进行调整和补充。
所以它基本上是贯穿于整个过程的。
ok接下来的一步是本章的重点战略设计。
什么是战略设计呢?
上一章我们已经提到d滴滴中对问题空间和解决方案空间进行分解的过程,就叫做战略设计。
他的目的是分解模型,从而控制模型的复杂性战略设计是DDD与传统建模和设计方法的核心区别之一。
我们来看一下这张图,右边这张图就是战略设计的输出,大家这里有个印象就可以了。
我们接下来的章节就会详细进行讲解战略设计,主要做三件事情领域划分,寻找线界上下文,也就是boundedcontest,我们简称为BC以及确定上下文映射,也就是上下文之间的关系,确定上下文映射的过程呢,其实还会决定在哪里加入防腐层。
本章呢?
我们会通过案例详细讲解战略设计的过程。
战略设计之后呢,就是战术设计了,战术设计是对线界上下文内部结构细节的设计过程。
他要决定BC内部的模型结构和完整的技术方案。
这个图是从领域驱动设计这本书里截下来的。
这些元素呢就是战略设计相关的主要结构元素,包括聚合实体磁对象工厂仓库模块啊服务等等。
由这些元素构成的结构确定下来之后呢,代码基本上也就确定了。
那么有个问题啊,战术设计包括编码吗?
ericevs曾经说过这个问题啊,他认为设计人员或者架构师是需要深度参与编码的。
所以这里我们认为战术设计是包括编码的,我们的课程,后面也会对代码进行讲解。
ok我们前面讲到了,要进行领域划分,要寻找线阶上下文,要寻找聚合等等。
那么怎么样进行领域划分呢?
怎么样寻找线接上下文,怎么样寻找聚合呢?
这就需要借助一些建模方法了,常用的建模方法有这么几种。
我们前面提到了两个,一个是domainstorytelling我们姑且翻译为领域故事陈述法,他跟我们经常提到的用力分析法是类似,我们认为没有本质区别,只是storytelling的工具会更成熟一点。
第二个呢是eventstormy,也就是世界风暴法,这个不用说是最主流的方法。
此外比较有影响力的,还有一个四色建模法和前面两种方法相比呢它并不是专门为滴滴滴提出来的,而且思路会略复杂。
一些偏冷门一点,但是也是非常有效的一种方法。
这几种建模方法里影响力最大应用最广泛的是事件风暴建模法,而最简单易用的是domainstorytelling。
因此,我们的课程里会主要对这两种方法进行讲解和应用。
ok我们来总结一下领域建模和设计,可以分为四步。
第一步,挖掘用户故事,我们要用文字进行简单的描述,要进行讨论,最终也要输出图形描述。
第二步,建立通用语言。
当我们用storytelling的方法挖掘用户故事的时候,同时其实就是在建立通用语言,通用语言要固化下来,可以用文字图形甚至代码。
第三步,战略设计,战略设计要对领域进行划分,要寻找线阶上下文,并决定上下文映射。
第四步,战术设计要决定BC内部的细节结构结构里面包含聚合实体合资对象工厂和仓库服务等等这些元素。
ok我们的这节课的内容就到这里,谢谢大家。
下节课再见。

2-3 如何描述模型?从用户故事到通用语言

大家好,上节课我们介绍了建模的整体流程。
整体流程里的第一步就是对用户故事进行分析,挖掘。
从针对用户故事的讨论开始建立通用语言。
本节我们就重点对这部分内容进行讲解。
有的同学可能会问,用户故事分析以及建立通用语言和战略设计是什么关系?
为什么要把他们放到战略设计这一章的开头来讲呢?
这是因为建模一定要从问题空间出发,而且通用语言也需要尽早开始出发。
因为无论是描述模型还是团队沟通,都需要依赖通用语言。
所以在正式开始战略设计之前,我们从用户故事开始对领域进行探索,可以使得开发人员能够迅速学习领域知识,并且在团队内部初步建立通用语言为后面的领域划分,寻找现接上下文乃至寻找聚合等等战略战术设计工作做好必要的铺垫,而且业内有观点的,也认为,通用语言的建立就是战略设计的一部分。
所以我们把用户故事分析和通用语言放到战略设计这一章的开头来讲是一件比较自然的事情,也可以为大家逐渐深入后面的学习打下一个基础。
在二点四节,我们以图文的方式对我们的案例系统的战略设计进行了整体的阐述。
在开始后面的讲解之前,可以先大致浏览一下二点四级的文章作为预习,可能有一些内容,大家目前还不能完全理解。
但是随着本章讲解的进行,大家的理解也会逐渐理析我们后面讲解的过程呢也会随时引用这篇文章的内容。
在本节中,我们首先会对用户故事进行介绍。
接下来呢会学习使用domainstorytelling,也就是用户故事陈述法进行用户故事分析。
在此基础上,我们会学习什么?
是通用语言,最后怎么样去建立通用语言。
大家知道业内都说程序员和产品经理是天敌关于程序员和产品经理相爱相杀的段子呢也很多啊,原因是程序员总觉得产品经理不靠谱,喜欢拍脑袋设计产品功能后面发现有问题呢又随意更改,导致开发团队做大量的无用功,最后产品呢也不一定成功,或者认为产品经理写文档专业缺少关键细节,导致很多地方需要程序员自己去猜,最后验收的环节呢又不满意又得改。
其实出现这种问题的原因,是因为捕捉用户需求,确定产品特性,本身就是一件复杂的事情,尤其像结合商业环境竞争对手乃至技术可行性之后,要考虑的因素就更多了,决策也要冒风险。
产品经理的工作呢并不像很多程序员想象,那么没有技术含量,正如大部分的程序员也并不是产品想象的那样,完全没有业务思维。
所以这么复杂且重要的事情,大多数产品经理其实是没有能力独自做决策的。
最好的方式是不要冒险,让产品经理独自决定产品功能,而是先从问题空间出发,对软研团队的关键人员,包括产品和程序员,围绕用户故事进行讨论,大致方案确定之后呢,在各自进行细化,基于讨论的结果。
产品可以细化具体的交互设计,而开发同学就可以进行建模和技术方案的设计的,这就是用户故事的意义。
什么是用户故事呢?
所谓用户故事,这是一种对软件系统特性的非正式的自然语言描述是敏捷软件开发中从终端用户的角度,对软件系统特性进行捕捉的一种方式。
用户故事描述的不同类型的用户需要什么,以及为什么需要它可以帮助我们创建需求的简单描述。
这段话太过抽象了啊,其实简单一句话,用户故事就是对问题的描述。
我们刚才提到,在软件开发的早期需求分析阶段,就让产品经理对软件的功能特性进行详细的设计。
不仅是一种时间上的浪费,也会让团队过早的陷入细节,用户故事就提供了一种恰到好处的力度,直接对需求,也就是对问题进行描述。
那我们来看一下怎么样构建用户故事呢?
构建简户故事有三个环节,第一,简单描述用户需求。
第二,围绕简单描述进行讨论。
第三,明确如何验证怎么样对需求进行简单的描述呢?
我们可以通过卡片,所谓卡片,其实就是用户故事的简述。
因为传统上敏捷软件开发,喜欢在白板上使用便利贴进行讨论和设计。
而用户故事的简述呢就可以写在一张便利贴上描述,一般采用这种模式。
作为什么样的角色,我想要做什么事情,以便达到什么样的目的,或者说实现什么样的商业价值,也就是whowhat和why接下来呢?
团队要围绕用户故事进行讨论,并且把谈话内容记录下来。
我们称为conversation最后后要确认验证方法,用户故事必须是可以验证的验证方法。
要尽可能的详细,一般是这样一种模式。
假设我是某某角色,在什么什么情况下,当我对系统做了什么,就会出现什么样的结果。
然片谈话和验证就构成了用户故事的三要素,也就是三c。
然后呢,其实就是cardconversationconfirmation三个单词的首字母,更具体的让我们来看案例。
我们的smartRM系统立项之后呢,产品经理梳理出来了,系统中的几个核心的用户故事,包括售卖机扫码支付购物柜门机免密支付购物售卖机的投放售卖机的撤销捕货和进行分析。
对于售卖机扫码支付购物这个用户故事来说,它的卡片就可以这样写包为用户。
我希望在售卖机上通过手机扫码支付购买商品,以便快速便捷的购物。
这就是按我们前面所说的whowhat和why的模式,然后我们围绕用户故事进行讨论。
这里我们把讨论内容也记录了下来。
我们点进去看一下,我们可以看到谈话,主要在产品经理和研发同学之间进行我们用p来指代产品经理,用d来指代研发。
在这里呢产品经理首先对用户在售卖机上购买商品的流程进行了一个简单的描述。
而研发同学呢则不断的提问期间呢,他们也有对用词进行统一,他们决定使用售卖机这个词来指代所有的售卖机设备。
那英文呢用manding的逊当讨论到失败处理相关的问题时呢,他们决定邀请相关领域的专家,并就是售卖机的运维人员。
我们用o来指代。
这样呢他们才能对售卖机有一个更深入的了解,以便对用户体验进行优化。
针对用户故事的这种讨论呢,可以让我们快速的明确业务流程,熟悉领域知识,同时建立统一的用语,并且让团队成员之间关于系统的理解达成一致,后面产品经理再出需求,文档就不会被喷了。
而研发同学的痛苦也会减少在对用户故事进行了详细的讨论。
而产品也完成了交互设计之后呢,我们需要把验收标准也确定下来验收标准要尽可能您的详细。
对于这个用户故事来说,验收标准可以这样写。
假设我是一名用户货到售卖机屏幕的商品列表上有商品ABC当我在售卖机屏幕上选择的商品a并扫描展示的二维码,完成支付之后呢,那么商品a就会从售卖机中弹出,我可以拿到商品a开发同学在写单元测试的时候,可以拿自段化做一个参考。
根据可以完成之后呢,产品或者测试同学也会按照这段话来进行验收。
我们已经把案例所涉涉到的用户户事的详细描述述都收到了smartRM通用语言文档这篇图文文章里面里面有关关于用户故事全部记录录和通语言相关的内容在需要了解smartRM系统的需求细节领域知识以及专业用语的时候,大家可以打开这个文档进行参参考。
据据我们才记记录案例的用用户事事讨论内容容需要发现什么问题呢?
就是我们谈谈话相是比比较松散的,没有什么章法,有没有可能漏掉什么关性性的时候呢?
会重的问题呢?
而在这样谈谈中,我们们其是把案难对谈话的内容进行详尽的记录的。
在现实中很难像我们文档里那样,最多只能记录一个简单的摘要。
在记录的过程中呢,也有可能会漏掉关键细节,有没有一种方法能够让我们讨论用户故事的率率,更而我们针对性更强,让我们能够迅速的明确业务流程,建立通用语言,并且同步的形成简要的文档呢?
很幸运。
这种方法和工具是存在的。
就是我们接下来介绍ddomastorytetelling就是是领域故事陈述法。
我们先来看一个图,这个图呢就是某电影票务系统进行storytelling的一次输出图中的人形图标,代表一种角色电脑图标代表一种系统对话框图标呢代表某一次的线下沟通。
而这个文件图标呢往往代表系统中的某种对象图中的线条呢包括两种,一种是从角色代者系统指向对话框,或者是对象的。
比如说像这个标记了askfor的线条,他们往往表示一种活动或者一种动作。
这个线条呢,它就表示顾客发起预定的活动。
而这个用蓝绿色标注的序号呢就是活动发生的时序。
另一种线条呢是从对象出发指向角色或者系统的。
他们往往表示对象和角色与系统的关系。
比如说对象的传递方向,或者对象是存在于系统之中的。
第六步呢表示的一个业务流程是怎么样的呢?
其实就很清晰了。
第一步,顾客向售票员发起预定号,求第二步呢,售票员去系统上的screenplay。
我们翻译成评估计划或者放映计划去他上面查找可用的座位。
第二步呢,售票员把座位推荐给顾客。
第四步,顾客确认座位。
第五步,售票员去系统上,把这个座位标记成已预留第六步,系统生成预留号。
第七步呢,售票员把预留号码告诉顾客这样一个图是怎么样画出来的呢?
其实就是通过类似我们刚才说到的关于用户故事的讨论。
在讨论中呢,可以由一个人主要进行发言。
第个人一般是产品经理或者领域专呢,可者是架构师。
第三步呢可以提问,也可以提出不同的意见,或者是自己觉得系统中有风险的地方在深入参与讨论人里面呢选一个人出来同步的画。
这个图图一般以是核心开发人员或者是产品经理,具体绘图呢可以使用这个工具叫做domainstorymodeler,这个工具是完全开源的啊,这里我们也附上了链接,大家可以进去把它下载下来。
这里我们点进去看一下,我们选择最新的发布,点击下面这个链接呢就可以下载它的压缩包。
解压之后就可以使用了。
接下来我们会用它来演示一下怎么样对我们案例中的售票机扫码支付购物。
这个用户故事来进行domainstory,telling我们进入到解压之后的目录里,使用浏览器打开index文件就可以进入工具的主界面了。
主界面有四个区域,左上角是标题和描述区。
在这里呢我们可以输入这个用户故事的标题和描述信息。
我们的标题是售票机扫码支付,购物描述信息是用户通过手机扫码支付,在货到售卖机上购买商品。
右上角呢是一个工具栏,其中有一些我们常用的按钮。
第一个按钮呢是我们的播放按钮主要用来进行一个场景走查,或者在讨论完成之后呢,对我们讨论的内容进行一个检查。
第一个按钮是加载已经保存的项目文件。
第三个按钮是保存项目文件。
第四个按钮是导出图片可以导出成历史VG或者PNG。
第五个按钮是我们的字典按钮。
第这个图里涉及到的所有的词汇都会出现在这个字典里面。
现在我们图是空的,所以这个字典是灰色的。
第六个按钮呢是我们的设置按钮,我们可以建立自己的图标体系,这里我们不用去动它。
第七个按钮是快捷键的设置。
下面呢就是我们工作的主区域了,左边我们可以选择我们需要的图标。
右边呢是白板。
接下来呢我就需要一人分十二节了。
因为正常情况下,domastorytetelling至少需要两个人,所以我们需模模拟一产产品经理和一个研发同学来进行讨论。
假设需要经理货货售卖机呢就是我本人现在老张说用户在货到售卖机上购买商品的第一步是在售卖机的屏幕上选择他要购买的商品,这里出现了一个词叫做货到售卖机。
作为研发的我呢不太明白,所以我就问用户是货到售卖机呢,老这这个时候就需进进行解释了。
他说,我们的系统中售卖机主要分为两种,一种自动结算的柜门机,用户扫码打开柜门机,拿商商品就可以离开了。
系统呢会自动进行结算,使用,免密支付扣除货款。
另一种呢,就是这里货货售卖卖了了户需要先扫码支付进行购物。
户户需要先选择商品,扫码完成之后才能拿到商品。
从构造上来说呢,货到售卖机,具体又包括弹簧货道的和蛇形货道的等等。
但是呢我们其实不用太关心他们的内部构造。
我们只需要知道货道售卖机是用扫码支付购物就可以了。
老张讲完之后呢,我就终于理解了。
于是我需要按照我的理解,把他刚才那句话给绘制出来。
我们在左侧选择一个角色图标,创建角色。
这个角色名字叫用户,我们可以看到右侧呢自动出现了这个图标,可以连向的图标。
我们在里面选择一个对象,我们创建一个新的对象,叫做商品这个活动的名字呢叫做选择在哪里选择呢?
在货到售卖机上,所以我们点击商品选择创建一个系统,这系统叫货到售卖机这个连线呢就表示我们的选择是在货到售卖机这个系统上进行。
老张看到这个图以后呢,马上就理解了,他表示认可。
那继续往后说,用户选择好商品之后呢,售卖机需要展示这个商品的支付二维码,用户会扫描这个二维码进行支付。
这两句话比较好理解,而且我也没有不同意见。
所以我需要继续往下画,我们点击货到售卖机,选择创建一个新的对象,这个对象叫做支付二维码,用户需要扫描支付二维码,说我们点击用户选择一条连线,连到支付二维码,它代表一个活动。
这个活动名字叫扫描,老张,又表示满意,继续往下说,用户完成支付之后呢,货到售卖金就会弹出商品。
所以我们需要从用户出发,创建一个新的对象,这个对象叫做支付,用户,需要先完成支付。
接下来呢货到售卖机会弹出商品。
所以我们选择货到售卖机,选择新建一条圆线到商品,再往上拉一点啊,看得更清楚。
那这个活动的名字叫做什么呢?
叫做弹出,但是这里我就有点疑问了,为什么货到售卖机,把商品送出去,是用弹出这个词来呢?
我有点纠结,所以我就直接问老张了,我说这里用弹出这个词合适吗?
老张说,对于部分货到售卖机来说,商品是弹出的,但是对于其他货到售卖机来说,弹出可能并不合适,但是我呢找不到更好的用词,但如我们就统一用弹出商品来表达所有货到售卖机的出货怎么样?
我认为这是ok的,毕竟这个问题并不重要,我们只是需要一个统一的用词而已。
但是呢还有另外一个问题,那就是货到售卖机弹出商品可能会失败吗?
毕竟作为一个老程序员,随时考虑失败,处理是我的职业习惯。
那这个时候呢,老张也懵了,他并不知道售卖机会不会出货失败,以及什么情况下会失败。
所以我们决定邀请售卖机的运维人员,老李来参与讨论。
老李作为售卖机领域的专家呢,对我们的疑问进行了解答。
他说售卖机的确是有出货失败的情况的。
比如说卡货或者网络故障都会导致出货失败。
在清楚这个情况之后呢,老张补充说,如果发现故障导致的弹出商品失败,我们需要取消订单,把货款退还给用户。
作为程序员的。
我呢在综合了老李和老张的陈述之后,认为这个失败处理的过程需要进行划分货到售卖机。
首先对故障进行检测,所以我们从货到售卖机出发,选择创建一个对象叫做故障。
那他要对故障进行检测,接下来呢需要将故障事件上报到系统后台。
所以我们点击它创建一个新的故障对象,创建一个系统,叫做smartIM系的后台,他需要把这个故障发送给系统后台。
最后呢smartIM系统后台需要对订单进行取消。
所以我们点击它选择创建一个对象叫做订单,他需要对订单进行取消。
老张看了以后呢,认为没有问题。
到这里,我们的分析过程就接近结束了。
但是为了避免出错或者漏掉关键性的细节,我们还要通过播放来走查一遍,我们点击播放按钮。
这个时候呢就要换成由我来进行陈述,老张在必要的时候进行纠正。
所以接下来我要把整个流程再走一遍。
第一步,用户在货到售卖机上,选择商品。
第二步,货到售卖机展示支付二维码,看到这里漏掉了一个活动,名字字叫择商品,所以我们退出来把它加上去。
我们继续第三步,用户扫描支付二维码,第四步,用户完成支付。
第五步,货到售卖机弹出商品。
第六步,货到售卖机检测故障。
第七步,他要把故障发送给smartIM的系统后台。
第八步,smartIM系统后台取消订单。
这一步呢也包含了退款。
老张看了以后呢,觉得有点问题。
因为这里六七八三步只有当弹出商品失败的时候才会出发,所以好像还少了点什么,所以我们退出来对这一点进行补充。
我们可以创建一个组,把六七八三步框起来,我们双击它可以进行标注,它是一个可选的过程。
所以我们标注成可选,仅当货到售卖机弹出商品失败的时候才有效。
这里我们把它往这边拉一点,这样可以看得更清楚一些,这样就没有问题了。
点击保存按钮或者controls可以保存项目文件也可以导出成图片。
所以我们导出成SVG我们刷新一下页面,看看能不能导入刚才保存的文件,点击导入按钮,选择项目文件可以看到是没有问题的。
其实呢也可以选择导入SVG文件,我们再刷新一下,点击导入。
这一次我们选择SDG文件可以看到,也是没有问题的。
这就是通过domainstorytelling进行领域故事分析的过程。
大家可以回顾一下,通过这样一个过程,我们达到了几个目的呢,通过这样一个过程,我们至少达到了三个目的。
第一,我们让整个团队对领域知识进行了学习和探索。
经过这个过程之后呢,产品经理老张和领域专家老领头脑里的知识,也传递到了我的头脑里。
而我呢通过提问和表达自己的关系也完善了老张对领域的认知,我让老张清楚了,还需要有一个故障处理的过程。
第二呢,我们其实已经开始了建立通用语言的过程。
整个对话中,我们有两处是对领域概念和用词进行了对齐,一个是货到售卖机,一个是弹出商品,我们统一了对事物的认知和表达方式。
这个呢其实就是在建立统一语言了。
还有第三点,我们抽取出来的关键性的对象和活动,并且初步建立了他们之间的关系。
这个图里面商品支付订单通用语言的故障等等,他都是关键性的对象。
这些对象在我们后面的设计和探索,其过程中会发挥作用。
我们可以点击这点按钮,这里呢会显示我们所有的活动和对象左边是活动,右边是对象。
讲到这里呢,我们已经不止一次的提到了通用语言。
下面呢我们来正式介绍一下通用语言到底什么是通用语言呢?
通用语言有三个关键点,第一,它是一种描述领域模型,并且基于领域模型的语言。
第二,团队在进行所有交流时都使用它。
第三代码中也要体现它后面两句很好理解,重点是第一句怎么理解。
为了理解这句话,我们来看这张图,外面的虚线圈代表真实世界里面的实线圈代表一个完美的领域模型,而可以很好的描述一个领域里边的虚线圈呢表示团队正在讨论的一个并不完美的领域模型。
平时说的自然语言平是用于描述真实世界的。
所以我们说它指向真实世界,而团队使用的领域通用语言呢则是指向的模型。
所以我们说通用语言是描述模型的最初通用语言指向的,并不是一个完美的模型。
但是我们还是要使用它。
因为只有这样模型的不完美,才会被暴露出来。
团队的所有成员在使用通用语言的时候,会发现它和自己大脑里的理解不太一致,要么是自己的理解出了问题,要么是模型有问题。
经过仔细辨别之后呢,如果是个人理解出现问题,如果理解就得到了纠正。
我果发现是领域模型有问题,我们就会对领模型进行优化,从而使讨论中的模型逐渐接近理想模型。
但是如果团队成员使用自然语言,而不是基于模型的通用语言进行交流,我们可能始终都无法发现我们对业务理解的问题或者模型本身的问题。
这就是为什么通用语言一定要是基于零模型的语言,通用语言到底包含哪些内容呢?
一般来说,通用语言,包括括括模模型里内操操作名称。
例如我们在对售卖机扫码支付购物这个用户故事进行storytelling的时候提取出来的商品支付维维码及及货售卖机弹出商品品的弹出等等。
这些词就是通用语言的内容容们可以作作为类名和操作名。
此外,当通用语言也包括在模型上加的规则则约束束,外括通售机中的商品卖完了要给他补充商品时,补充的商品数量不能超过售卖机的容量。
这就是规则和约束,也是属于通用语言中的内容,最后应用于模型上的模式,比如工厂仓库等等,甚至包括现界上下文这种词汇,也是通用语言的一部分。
让我们会回过头来看一下我们stortelling的输出。
这个图它它满足通用语言的定义吗?
首先它是描述模型,并且基于模型的吗?
显然它是的这个图通过对业务流程的描述,建立了一个模型的雏形,不一定足够准确和细致,但是已经包含了一些关键的类和操作的,对这些关队内和操操作进行的名名,而述是他们之间的关系。
而且呢这个图非常简洁,简洁到已经无法再进行删减了,完全是基于模型的。
其次,它是团队进行所有交流都使用的语言吗?
而进行storytelling的时候,常常会对用词进行统一,这些统一的用词呢会成为后续交流的标准。
所以它是团队后续进行所有交流都使用的语言,这个图里的词汇会出现在代码中吗?
提取出来的商品支付二维码。
其次,它等词汇经过固定之后,最终可以也必须作为代码中的类或者方法名storytelling的过程。
其实同时也就是在建立通用语言的过程,它输出的这个图就是通用语言的一部分。
所以我们把所有重要的用户故事的图都收录到了通用语言文档里。
此外我们还可以更进一步把这些词汇抽取出来,列到表格里,将它们固定下来。
就像这个表格,这个表格我们也收录进了通用语言文档,让我们来回顾一下这节课的内容。
这节课的内容可能有点多,但是重点只有两个,一是我们学会了如何使用domainstorytelling,也就是用户故事陈述法对领域中的用户故事进行了分析。
并且在这个过程中,建立通用语言domainstorytelling本身就是一种建模法。
二是我们学习了通用语言的定义作用和构成,以是如何形成通用语言。
我们把storytelling的图和整理的词汇表对录进了通用语言文档里,社会有助于我们对通用语言的建立和完善。

2-4 SmartRM系统整体战略设计

2-5 SmartRM通用语言文档

2-6 分解问题:领域划分和子域

大家好,这节课呢我们就要开始学习滴滴滴的战略设计中一个非常重要的内容了,也就是领域划分。
在这节课中,我们首先会了解什么是领域划分,以及什么是词域,然后会分析为什么要进行领域划分?
最后我们会学习一种基于用户故事分解的领域划分方法。
那么先让我们来了解一下概念,什么是领域划分呢?
所谓领域划分,就是指以分离关注点为原则,对问题空间的划分,将大的领域划分成子域。
那么什么是子域呢?
网格的也就是实现领域驱动设计。
这本书的作者说过,领域是某个组织所做的事情,以及其中所包含的一切。
那么既然领域划分是对问题空间的划分。
当然子域就是划分之后某个方面的问题以及解决它所涉及的一切了。
前面我们的课程提到smartRM系统立项之后,产品经理梳理出来了。
六大核心顶层用户故事,分别是售卖机扫码支付购物柜门机免密购物售卖机投放售卖机撤销补货和经营分析。
我们前面说过,用户故事就是对问题的描述。
从这一点上来说呢,用户故事和领域划分其实是很搭的。
那么这六个用户故事,他们所描述的问题可以进行划分吗?
或者说可以进行分类吗?
很显然,前面两个售卖机扫码支付购物柜门机免密购物解决的是交易问题,后面四个售卖机的投放撤销补货经营分析解决的是运营问题,他们的功能区分非常明显,面向的用户角色不一样,操作入口也不一样。
那我们首先就可以把smartRM领域划分成这两个大的子域交易域和运营域。
他们一个解决交易问题,一个解决运营问题。
这样我们其实就已经进行了初步的领域划分了。
那么这样的划分是不是就已经足够了呢?
领域划分要到什么力度才算合理呢?
为了回答这个问题,我们要搞清楚为什么要进行领域划分?
在现实中,有一种比较常见的开发流程和分工模式。
这种模式是这样的。
首先,产品经理提需求写文档。
接下来开发leader把需求分配到不同的开发同学进行技术方案设计和编码。
大家觉得这种以需求为力度进行分工协作的方式有没有问题呢?
拿我们的案例来剖析一下,就可以看出问题来了。
假设我们把产品梳理出来的六个顶层用户故事,逐个分配给六个不同的开发同学会出现什么状况呢?
请注意在项目时间压力比较大,而研发leader又没有做太多思考的情况下,这种分工是很可能出现的,看起来好像并发度很高,大家同步开发。
但这种分工协作方式有两个致命问题。
先来看第一个问题点和领域知识重叠,我们以售卖机扫码支付购物和柜门机免密购物。
这两个用户故事为例,这两个用户故事如果你深入进去,会发现他们涉及有很多个问题,其中包括这么三个问题,一是支付。
二是交易流程。
三是设备操控假设这两个用户故事被分配给了两组不同的开发。
那这两组同学就要同时分别关注这三个方面的问题了,压力很大不说,而且还从不劳动了。
大家知道同时做几件事情是很难做好的,更何况还有重叠的工作效率极低,不说还有冲突。
再来看第二个问题,不仅关注的问题和领域知识重叠了,模型也重叠了。
从这两个用户故事分别分析下去都会提炼出来几个关键的概念。
商品订单售卖机这些概念最终都会转化成代码。
那么这两组同学又会出现重复劳动和冲突。
最后这几个概念对应的类的设计到底谁说了算呢?
这两个问题最终导致的后果归根结底,一是效率低。
二是职责重叠导致冲突而解决他们的方法,就是领域划分。
我们把这两个用户故事涉及的问题细分后形成几个子域,其中就包括了支付域交易域和设备域不同子域交给不同的人来负责。
而商品订单售卖机这几个概念呢,最终会落入不同的子域。
其实确切的说概念的圈定是我们后面要介绍的先界上下文的职责。
但是线界上下文和子域是紧密相关的。
在理想的设计下,子域和线界上,下文是一一对应的这样的划分以后呢,问题就迎刃而解了。
不同的开发同学面向的是不同的问题。
他们的工作更聚焦了,也没有重复劳动和冲突的问题了。
那么接下来的问题就是怎么样进行领域划分呢?
在架构师本人能力比较强,经验比较丰富的情况下,直接通过直觉和经验去进行领域划分也是ok的。
而且其实软件系统的设计也有它艺术性的一面优美的划分本身就是解决方案的一部分,领域划分,也是能体现架构师的能力的。
但是那就没有一种能对领域划分有指导和辅助性作用的固定方法了吗?
毕竟技术人员的能力的强化是需要时间的。
而且架构师的工作呢也最好能有一种方法进行检验,最好能保证结果的准确性和效果的稳定性。
刚才其实我们已经透露过一点,领域划分是对问题空间的划分,而碰巧呢?
用户故事也是对问题的描述。
那么是否可以借助用户故事对领域进行划分呢?
就像我们刚才划分出交易域和运营域那样。
接下来呢我要介绍一种我们在实践中检验过行之有效的方法,叫做基于用户故事分解的领域划分法,它利用的就是用户故事和领域划分的相关性。
这个图大家还有印象吗?
这就是前面课程里面,我们使用的domainstorytelling分析售卖机扫码支付购物。
这个用户故事得到的图。
之前我们说到storyteelling有很多意义,比如熟悉领域知识,建立通用语言,甚至建立模型。
其实呢他也可以认为是对用户故事的分解,每个序号代表的语句可以认为是一个子用户故事,比如用户在货到售卖机上选择商品货到售卖机展示支付二维码,用户扫描支付二维码,其实都是指用户故事,他们的有序进行构成了外层的用户故事。
这些子用户故事有一些非常具体具备了,原子性已经可以直接落地,不能再拆了,甚至对应到代码里就是一行代码。
有一些呢仍然包含了大量的细节比较复杂。
对于这些复杂的用户,故事可以进一步进行分解。
比如货到售卖机展示支付二维码,这个只用故事就包含了较多细节,我们可以对他进行进一步的分解。
也就是说,对他再做一次story,telling得到了这个图货到售卖机,首先需要向smartRM系统后台请求支付URL请求里当然会包含商品信息smartRM系统后台收到请求后呢,根据商品信息生成订单,然后根据微信的支付流程需要去微信支付平台获取codeURL也就是预支付交易链接,最后货到售卖机生成的二维码,展示二维码,这样的分解可以一直进行下去。
那么分解到什么程度才能停下来呢?
对于领域划分来说,当分解之后的所有子用户故事已经足够聚焦,聚焦到仅关注一个很小的领域问题,甚至变成了一个原子操作,那么分解就可以停止下来了。
最后呢我们得到了这样一颗树状图,树上的节点就是用户故事。
他们可以看成是一个个或大或小的需要。
我们正在进行建模和设计的软件系统去解决的领域问题。
当然,当分解进入到叶子节点时,问题和答案的界限就已经变得比较模糊了。
这个过程非常类似敏捷软件开发里常用的分解方法叫做storymapping。
我们对交易域里的两个用户故事进行这种storymapping,得到了两棵树。
现在我们看到这棵树呢对应的是售卖机,扫码支付购物。
这棵树对应的是柜门级免密购物,接下来的事情就显而易见了。
我们需要对这些故事进行分类,我们按照关注点,对他们进行聚类。
先来看一下售卖机扫码支付购物,这棵树用户选择商品这个节点和他的子用户故事可以认为解决的都是交易问题,货到售卖机展示支付二维码,看起来有点像解决的支付问题。
但是呢其实它这里展示的是所选商品的支付二维,它还包含了根据所选商品生成订单的操作。
所以它解决的仍然是交易过程中的问题。
它的子用户故事,包括生成订单,请求支付urll取codeURL生成支付二维码,展示支付二维码。
这里这个内层的展示支付二维码和它外层的相比仅仅是UI展示其实已经不属于领域问题了。
那其中生成订单解决的是交易问题。
而其他的呢我们则可以认为解决的是支付问题。
当然,除了展示支付二维码,这个已经分解到不属于领域问题的子用户公式货到售卖机弹出商品检测故障上报故障。
很显然,解决的都是设备操控的问题,取消订单呢本身解决的是交易问题,但是它包含的退款这个子用户故事解决的是支付问题。
同样的,我们对柜门机免密支付购物对应的这棵树,也可以进行这样的归类。
其中我们得到了这样一张图,我们把每个类别的节点分别穿起来给每个类别起个名字,分别是交易域支付域设备域和用户域柜门机免密购物分解后,有用户注册和用户登录的操作。
我们把它归到用户域里,通过用户故事之间的层次关系,我们又可以得到各个子域的引用关系,或者也可以说是依赖关系集成关系,交易域需要引用支付域设备域和用户域。
就这样通过故事分解,我们把原先的交易域分解成了更合理的四个子域。
同样的,我们对所有顶层的用户故事都可以进行这样的解剖和归类,最终可以得到smartrm系统全局的领域划分方案,我们打开smartRM系统整体战略设计。
这篇文章看一下,这里有完整的解剖和分类图,通过对运营域剩下的四个顶层用户故事的剖析,我们发现还需要独立出来一个值域叫做商品,它包含了商品信息获取和运营挑选商品,也就是选品相关的内容。
最终我们得到了六个子域,他们分别是肖易宇支付域用户域设备域运营域和商品域。
我们也同步得到了他们之间的依赖关系。
所以我们通过故事分解得到了这样一个整体的划分划分之后,得到的子域呢都有了特定的含义和明确的职责。
其中,交易域负责实现面向终端用户的商品交易用户购买商品时使用的设备前端界面完成交易的后台程序,都是在这个域里设备域负责实现售卖机设备的管理和操作。
他会将售卖机设备抽象成合理的售卖机模型,并且通过api对设备进行远程操控。
我们的设备会上云有可能是阿里云,也有可能是aws或者是其他的什么IOT云,他们不同型号可能会有自己的雾模型。
然后这些细节对我们这个领域并没有那么重要。
最终经过领域专家和我们的开发人员在这个子领域中的工作,其他子域看到的是售卖机的领域模型。
这个模型只包含了我们在smartRM系统中真正关心的部份支付域呢负责实现扫码支付免密支付等支付相关的功能。
在柜门机免密购物的流程中,用户需要注册和登录,因为系统需要分辨他们的身份记录,他们的状态和交易相关的信息。
比如说在扣款过程中,需要知道他们的账号,那这些就交给用户域去做运营域呢负责帮助运营人员制定最优的运营策略,并且将他们落地。
除此之外,无论是交易域还是运营域,都需要和商品打交道,他们需要获得商品详情。
一淫域呢需要进行商品搜索商品选品等等。
这些都是在商品域完成以上呢,就是我们基于用户故事分解进行领域划分的整个过程。
ok我们来总结一下这节课的要点。
首先,领域划分是对问题空间的划分,划分的原则是分离关注点。
其次,领域划分的最重要的目的之一是解决分工协作的问题,要基于子域进行分工写作,而非基于需求,否则效率低下,而且容易出现冲突和重叠。
最后,基于用户故事分解进行领域划分,可以让领域划分清晰化。
但是这里呢我们也要强调一句,系统设计仍然有它的艺术性,同一个问题分解的方式可以不一样。
实际上分解本身就是从问题空间向方案空间进行的过程。
分解方案就是解决方案的一部分。
我们提供的方法只是一种辅助分析的方法。
这种方法是有效的,但它不是机械的操作流程,分解的过程中,仍然需要大量的思考和团队的讨论。
不同的分解方式会导致不同的方案和效率。
好的,分解才能导入好的方案。
ok这就是本节课的内容,谢谢大家,我们下节课再见。

2-7 确定系统最核心的部分:核心域和精炼

大家好,上节课,我们对我们的smartIM系统进行了领域划分。
我们划分出来六大子域初步分析,我们就发现这套系统的复杂度不低了。
但是我们的开发团队呢只有十人左右自身开发更少,只有三到四人。
那么我们要怎么投入研发力量呢?
平均分配吗?
平均分配当然不合理。
那么怎么样分配团队的研发力量是最合理的呢?
这就要借助这节课,我们要学习的内容了,核心域和精炼。
这节课我们要学习的内容,包括子域的类型核心域的意义,以及什么是精炼和如何进行精炼?
对于复杂的系统进行领域划分之后,可能会形成很多的子域。
那么这些子域在职能和地位上是一样的吗?
当然不一样。
ericevans最早把子月分成两种核心域和通用子域。
其中呢核心域指的是系统中代表了产品的核心竞争力的部分。
如果你的产品要在市场上生存,那么你在核心域上的表现就需要比竞争对手强通用子域这里就有点问题了。
从字面意义看,通用子域应该具备通用性。
但其实领域中,除了核心域的其他子域并不一定都具有通用性。
所以后来wburden在他的书里引入了第三种类型,叫做支撑指域。
如果一个子域不是核心域,但是有很多子域都要依赖这个子域,那么它就是通用子域。
如果它既不是核心域,也不是通用子域,那么它就是资深子域。
其实对我们来说,最重要的是把核心域从系统的其他部分区分出来过多纠结概念的意义其实不大。
那么滴滴滴为什么要强调把核心域显示的区分出来呢?
其实就是为了聚焦大家有没有发现一个问题?
很多团队里的技术大牛其实都不太喜欢做业务开发,认为太low,没有技术含量,而是喜欢把多数精力都投入到底层。
比如说框架层的开发力。
前几年微服务的APM技术,包括像分布式调用链跟踪这些技术还没有成熟,是研究的热潮。
很多同学投入精力。
在这一块,包括甚至有一些业务开发的团队,也分出了一部分精英力量去开发了一个类似的框架。
对于技术人员来说,追求技术深度是绝对有必要的。
但是,滴滴滴认为,对于项目和产品来说,最核心的技术人员应该把精力投入到核心域的设计和开发里来,尽量减少团队在其他部分的投入,这样才能高效的提升产品和业务的核心竞争力,并且使公司的利益和个人发展形成良性循环。
这里我们插入一个话题,以前有人问我,程序员到底应该做基础架构,还是做业务架构,做基础架构呢?
能够提升个人的技术能力,而做业务架构呢?
能体现个人在业务中的价值,更容易提升个人绩效,获得在公司内部的晋升机会,那么到底应该如何选择呢?
这里我要说的是关键点,其实并非是从事什么技术方向,任何技术都有过时的一天。
但是任何领域的核心域的范围呢,都是相对于稳定的。
对于零售来说,核心域是供应链,对于游戏来说,核心域是游戏的核心玩法。
对于广告来说,核心域必须围绕着提升转化率。
对于云来说,核心域就是基础设施了。
当你身处于一个领域中时,你只有做核心域才能得到更多的发展机会,增强个人的竞争力。
同时兼顾个人和公司的利益。
所以如果你想从事技术架构相关的工作倒推一下,应该去云厂商或者公司内部的基础架构部门工作。
因为在那个领域基础架构才是核心域,而不要选择在业务开发团队里做技术架构的工作。
如果你从事的是业务开发呢,则应该尽可能的深入业务的核心域,把精力放在解决最重要的问题上。
而在这个过程中,你仍然可以发现打磨技术的机会,因为公司和公司产品和产品之间,在核心域的竞争仍然会不断的对技术提出越来越高的要求。
ok讲到了核心域,就不得不提到另一个概念叫做精炼,他们经常是一起出现的。
所谓精炼,顾名思义,就是不断的提炼和压缩的过程。
通过精炼,我们分离出领域中,普通的部分最终得到领域中极致精华的核心域。
在这个核心域中的每一点滴的工作都会产生最大的价值,而且不受系统中其他部分的干扰精炼,可以让团队用尽可能小的代价换取最大的成功概率。
那么怎么样去精炼呢?
其实和我们日常生活中的经验相似方法有两种,一种叫萃取。
就是随着我们对领域分析的深入,我们逐渐发现最能影响产品成功的关键问题是什么?
从而把它单独提取出来,类似把咖啡从咖啡豆里萃取出来,另一种呢叫分离。
就是随着我们对问题的分解,我们逐渐剥离出来。
对领域的核心问题影响不大的部分,那剩下的部分就成为了更为精华的核心域,类似用筛子把沙子从金沙里过滤掉,只剩下金子。
其实呢前面的课程里面,我们已经做了一次经练了我们从smartRM的交易域分解出来,支付域设备域和用户域。
其实就是精炼的过程,剩下的交易域,它就是系统的核心域之一了。
后面随着我们设计的深入,我们会再次提到精炼的概念,并且应用它。
那么我们从smartRM系统划分出来的子域,哪些是核心域呢?
想必大家已经想到了对smartRM系统来说最重要的问题。
一个是怎么样去保证用户的体验,使得用户愿意在我们的系统中购物。
另一个呢就是怎么样帮助运营实现运营利润的最大化。
所以核心域就是交易域和运营域,其他的子域要么是通用子域,要么是支撑子域,我们呢还是那个观点区分,所谓的通用子域和支撑子域并没有那么重要。
如果一定要区分呢,我们可以认为这里的商品域和设备域是通用子域,支付域和用户域是支撑子域。
ok我们简单总结一下这节课的几个关键点。
首先,战略设计要明确,核心域,核心域是代表领域中产品的核心竞争力的部分团队的主要力量,要毫不犹豫的投入到核心去中来,尽量减少非核心域的投入。
其次,核心域是通过金链得来的,金链有萃取和分离两种手段。
最后呢,从个人发展角度上来说,我们建议程序员也要尽量投入核心域的工作,这样才能在公司利益和个人发展之间形成一个良性循环。
ok这就是这节课的内容,谢谢大家,我们下节课再见。

2-8 分解模型:限界上下文

大家好,这节课呢我们要学习的是滴滴滴中最重要的概念之一,同时也是难点之一。
线界上下文切阶上下文是领域驱动设计最为核心的概念。
如果把领域驱动设计看作一个领域,那么现阶上下文就是它的核心域。
这节课我们要学习什么内容呢?
首先我们会了解线阶上下文的定义学习,什么是线阶上下文?
接下来呢为什么需要线阶上下文,它的意义和作用是什么?
然后最重要的是,我们要学习如何应用,现阶上下文来划分系统,也就是如何划分线阶上下文。
最后,由于微服务技术已经发展成为当下的主流技术。
而d滴滴与微服务也有着千丝万缕的关系。
我们也会对d滴滴与微服务的关系进行讲解。
需要说明的是,在我们课程的第五章中,我们会用一整章的内容对d滴滴和微服务的关系,以及他们在项目中如何结合应用进行深入讲解。
所以本节中我们关于d滴滴和微服务的内容只是简明概要的一个先导性的讲解。
ok让我们进入主题关于现阶上下文网论呢,也就是实现领域驱动设计。
这本书的作者在他的另一本书领域驱动设计精粹里面有提出来过他的定义。
我把它翻译成了中文现界,上下文是一种语义上的上下文边界。
意思是在这个边界里的软件模型组件都有它特定的含义,并且做特定的是一个现界上下文内的组件,都是上下文特定的,并且语义明确的这句话有点抽象,怎么理解它呢?
其实我们在第一张讲d滴滴的核心思想和解决的痛点问题时,就给过一个人民币的例子。
大家应该还记得一张简单的人民币钞票在面对不同场景不同领域时,可能可以用很简单的模型来描述,它也可能需要用非常复杂的模型来描述。
它其实就是因为面向不同问题,或者说不同场景不同领域时,其所展现出来的语义是不一样的。
同样的,在我们的案例smartIM系统中也有这样的例子,比如同一台售卖机,在运营人员眼里和在用户眼里是一样的吗?
当然不一样,在运营人员眼里,其实一个库存不断被消耗的,又需要及时进行补充的商品容器。
而在用户眼里呢,它是一台吞钱吐商品的机器。
所以当运营人员和用户在他们各自的场景或者说上下文里提到售卖机这个词的时候,它的意思其实是有差别的。
如果我们要让这个词具备单一的含义单一的作用,那就需要明确指出来它是在哪个上下文里。
换句话说它的现界上下文是什么?
这就是现界上下文的本质含义,理解了现阶上下文的含义。
我们其实就不难理解,为什么我们需要它了?
首先,自然语言本身就是模糊的同一个词,在不同场合不同。
上下文里通常有非常不同的含义。
比如这段对话,甲说,你这是什么意思?
乙说没有什么意思,一点小意思,请问这里的意思到底是什么意思呢?
其次,同一个事物面向不同的场景,有不同的模型。
这一点呢从我们的案例就能够看得很清楚了。
所以我们需要用线阶上下文来分解模型控制复杂度。
如果不分解会变成什么样呢?
我们最后就会得到这样一个大泥球的模型系统会非常难以实现和维护。
最后呢线阶上下文它也是分工的单位模型,分解以后,天然的具备了高内聚和低耦合的特性。
所以团队的分工协作非常合适,基于现阶上下文进行不同团队负责不同的线界,上下文,有的同学可能会问,前面的课程,不是说过分工协作要基于子域吗?
这里怎么又变成基于现接上下文了呢?
我们说过,在理想的设计下,子域和现接上下文是一一对应的子域,属于问题空间。
而现接上下文呢属于解决方案空间,所以这两种说法并不冲突,而且在建模和设计过程中,现阶上下文是后于子域显现出来的。
所以在前期的分析阶段,我们的分工的确需要基于子域,但是在后期进入战术设计阶段,就是基于线阶上下文了。
当然子域和现阶上下文的关系很多时候并不是理想中的一一对应。
这一点我们本节后面也会讲到了,但是并不影响这里的结论OK既然线阶上下文这么重要,那么我们怎么样应用它对系统进行划分呢?
或者说我们面对一个系统时,怎么样把它划分成不同的线阶上下文呢?
我们怎么样找到这些边界识别出它们呢?
这里我们介绍三种方法,第一种基于domainstorytelling的方法。
第二种,基于事件风暴法以及第三种基于子域概念提取法。
我们先来看第一种基于domastorytelling的方法。
在前面的课程中,我们已经详细学习了domainstorytelling这种建模方法,我们用它来建立通用语言,用它来进行领域划分。
但是我们也说过,它本身是一种建域方法,但也可以用来深入解决方案空间建立模型。
所以我们这里也来看看是否可以用它来进行线阶上下文的划分。
之前我们分别对几个顶层的用户故事进行了sorry,telling这次呢为了更直观的划分间接上下文。
我们通过domainstorytelling建立了整个领域的大图,我们把它放大看一下。
由于之前已经学习过domainstorytelling的过程,这里为了节约时间呢,我们就不再对整个绘制的过程进行演示了,我们把它播放一遍就可以了。
在一切的开始,首先运营需要决定在什么地方放置什么型号的售卖机,我们把这个动作叫做投放投放到的那个地点呢,我们叫做点位,点位是有很多特性的啊,比如名称坐标标签等等。
所谓标签呢,就是指这个点位有什么特性?
周围的年轻人多呢,还是老年人多是运动场所,还是学校等等等等。
投放过去的售卖机呢,包括货到售卖机以及我们所说的柜门机,其实就是自动货柜机或者称货柜机。
但是我们的通用语言里习惯把它称为柜门机,这个就不重要了。
只要在项目里,大家用词统一就可以了。
接下来呢运营需要决定在售卖机里面放什么商品以及放多少和什么时候对售卖机里的商品进行补充。
按照领域专家的说法,运营需要制定库存计划。
库存计划里面包含了商品组合每种商品的最大库存和安全库存。
所谓安全库存呢,就是指当商品库存低于多少时,就需要对他进行补充了。
库存计划会每天定时执行。
执行之后呢,会生成配送订单,发送到配送中台。
这里的配送中台是指客户的配送中台啊。
对我们来说呢,就是外部系统了,然后配送中台会把配送订单分配到配送员,配送员,再根据配送订单,把商品配送到指定的售卖机。
最后呢,用户才能在售卖机里购买到商品。
那么从这个图里我们能看到几个,现在上下文呢,其实有一个重要线索商品和售卖机这两个概念在这个图里有出现过多次,每一次的含义有没有区别呢?
首先看售卖机运营投放售卖机配送人员配送商品到售卖机,用户在售卖机上购买商品运营用户配送员,他们眼里看到的售卖机是一样的吗?
当然不一样,运营看到的是容纳和消耗商品库存的容器,用户呢看到的是收钱土商品的机器。
而配送员则主要关注的是收货地址和操作手册。
他就像是一个收货,有点麻烦的收件人,同样的他们眼里的商品也不太一样。
所以这里至少有三个线下上下文,对不对?
我们大致画一下,这样呢我们就至少能识别出三个系列上下文了,分别是运营上下文交易,上下文和配送上下文。
但是问题来了,有的同学可能已经想到了,就是他们和我们前面划分出来的子域并不是一一对应的。
这是什么原因呢?
首先缺少的几个上下文,包括支付上下文用户上下文设备,上下文和商品,上下文并没有消失。
而是隐藏在更细节的模型里了。
我们在讨论和绘制系统的大图时,往往力度是比较粗的。
其中很多活动如果深挖下去,会有大量的细节,比如用户购买商品这个活动如果展开,我们就会发现用户上下文支付上下文以及设备上下文运营,制定库存计划。
这个活动如果展开,我们就会发现商品上下文那么多出来的这个配送上下文是怎么回事呢?
其实对应的配送域是存在的。
通过我们前面课程里的用户故事分解法,我们也能够发现它。
但是它并不在我们的系统内部。
因为我们在立项的时候就已经很清晰的了解到了配送问题,我们是依赖客户的配送中台来解决的。
我们不会投入任何精力在它上面。
所谓配送域,在系统的外部,我们在领域划分里,也就没有把它列出来。
那么这里在划分线界上下文的时候,我们需要把它找出来吗?
是需要的,因为我们需要集成配送状态,需要明确内部线接上下文和它的关系。
其实如果我们把运营投放售卖机这个活动展开的话,还会发现另外一个隐藏的现接上下文叫做ERP系统。
上下文,它代表客户内部的ERP系统。
我们通过它来执行售卖机的投放订单,最终完成售卖机的安装。
这就是通过domesticteling划分线接上下文的方法。
我们刚才识别边界,主要利用的是概念之间的语义差别。
其实边界还有其他的标志总结下来,有三种第一种对象和对象之间的单向联系。
其实在我们的案例中也是有体现的,就是从库存计划到配送订单的这个箭头。
第二种语义区别,也就是我们刚才主要利用的特征。
需要注意的是,如果我们需要通过语义区别去划分线下上下文,那我们在进行讨论和画图的时候,就要注意要用不同的图标来代表不同含义的概念。
比如如果售卖机和售卖机不一样,我们就要用两个图标来代替它,而不能偷懒用一个图标,然后不同上下文的对象都指向它。
第三种活动的触发方式不一样。
在我们的案例中,库存计划的执行是定期出发和售卖界的投放是不同的触发方式。
那么他们是不是应该在不同的现阶上下文中呢?
这个问题会带出一个非常重要的问题。
现阶上下文到底是大一些好还是小一些?
好,到底是多一些好还是少一些好呢?
系统的划分是否有一个标准呢?
这里首先要说的是系统的建模和设计,并没有一个所谓的正确答案。
包括这里我们提到的边界的标志也并不是绝对的。
有时候三个标志都具备了,也并不表明,边界就一定存在。
而有时候呢,即使一个标志都没有,我们也可以考虑加上一条边界。
当然坏的方案都有一些类似的味道。
但是除此之外呢,不同的方案会有不同的优势,这时候的选择就会体现出架构师和团队的能力了。
这就是我们之前讲到过的系统设计中的艺术性。
但是在项目前期的战略设计环节,对系统进行切切上下文的划分,我的建议是宁缺毋滥。
比如这里我们的确可以在售卖机投放和库存计划,这里设置一条边界,把运营上下文划分成两部分。
但是这里存在一种可界,那就是售卖机的投放和库存计划的制定其实是紧密相关的。
因为本质上售卖机的投放解决了也是库存问题。
如果我们过早的把它们划分成两个不同的上下文交给不同的团队去做,会不会丧失一些在后续深入设计的过程中,本来有可能发现的全局优化的机会呢?
但果他们的确属于不同的上下文,但是他们迟早会发现,还记得我们前面提到过的建模蜗牛吗?
调整战略设计的窗口并不会关闭。
okdomainstorytelling是一种比较新的建模方法提出来的时间大约是在二零一八年前后,目前的影响力还不太大。
因比之下呢,eventstormy也就是事件风暴法,就可谓如日中天了。
事件风暴可以说是DDD中最重要的建模方法,借助世界风暴,我们也可以进行线接上下文的识别,我们会得到类似这样一张图,这是在白板上使用便利贴讨论的结果。
不过世界风暴涉及一些战术设计的概念。
所以我们在第四章才会对它进行详细讲解。
到时候呢我们会回过头来看一下怎么用世界风暴进行线界上下文的识别。
这里呢我们就先留一个悬念,暂时有一个印象就可以了。
通过前面domainsorykilling进行线界上下文的划分过程。
我们其实能够感觉到一点,那就是我们如果想比较完整的挖掘出所有线界上下文可能需要钻到业务活动的细节里面。
也就是说可能要挖的比较深,其实世界风暴法也有类似的问题。
但是我们可能希望在初期的战略设计能够比较快速简便的发现主要的线列上下文。
那么有没有这样一种方法呢?
其实是有的,就是我们接下来要提到的基于子域概念提取的方法。
我们在进行领域划分时,对smartRM的用户故事进行了分解和分类,形成了这样的一个图。
现在呢我们把各个子域里的关键概念提取出来。
例如,对于支付域来说,关键概念是支付URLcodeURL也就是预支付交易链接,支付二维码支付扣款支付协议。
对于交易域来说,关键概念是货到售卖机商品商品列表,支付二维码订单柜门机和柜门机二维码。
对于用户域来说,关键概念是用户和账号,以此类推。
这里呢我们也需要单独提一下商品,上下文和商品中台上下文。
我们知道在零售系统里面,商品是非常重要的概念。
在我们的系统中,多个子域都会依赖到商品域。
我们在领域划分时,虽然知道商品信息获取和选品相关的问题,可以借助客户的商品中台,但是仍然保留了商品域这个子域。
因为我们并不能确定客户的商品中台是否能完全满足我们的需求,而且不同客户内部的商品中台也不太一样。
所以我们需要保留自己的商品,上下文对外部的商品上下文做一个转换,然后提供给内部的其他上下文,我们把子域的划分和现阶上下文的划分结果进行整合,就得到了这张图,上面也通过连线显示了现接上下文之间的联系。
最后呢我们要提一下线阶上下文和微服务的关系,这也是很多同学关注的内容。
其实一句话就能概括微服务是线接上下文的一种实现方式。
我们知道微服务架构的反面是什么?
是单体架构,对不对?
那他们的区别是什么呢?
他们的区别从上面的两个图我们就能看清楚了。
在单体架构里面不同服务。
他们往往是共用网关和数据层的多个服务,往往也挤在一个应用里面,然后一起部署。
虽然单体架构也可以弹性扩缩容,但是不同服务是挤在一起的。
到了微服务时代呢,我们倡导的是每一个服务都有自己独立的体系,独立的网关独立的数据层独立的部署节点。
所以我们可以看到微服务架构其实主要是关于隔离性的。
但是微服务的推行需要依赖强大的基础设施,否则会变成灾难。
这个我们在第五章会进行详细讲解,还是让我们回到这里的问题。
不管是单体架构还是微服务架构,都可以实现线接上下文。
所以微服务只是线接上下文的一种实现方式。
一般来说一个上下文对应一个服务,这就是微服务和线阶上下文的关系OK我们来总结一下,首先什么是线阶上下文呢?
所谓线阶上下文,就是在解决方案空间对模型的分解单位,这一点和子域是相对应的,子域,是对问题空间的分解子域和线阶上,下文往往是一一对应的。
但是有一种特殊情况,那就是当我们需要集成外部系统或者是旧系统时,这里从我们的案例就可以清晰的看到我们的支付上下文集成了微信支付平台的上下文,我们的商品上下文写成了商品中台的上下文,我们的支付上下文集成了微信支付平台的上下文。
所以支付域除了包含支付,上下文,还包含了一部分微信支付上下文。
同样的,我们的商品上下文集成了客户的商品中台上下文。
所以商品域除了商品上下文,还包含了一部分的商品中台上下文。
而我们的运营上下文呢集成了ERP系统上下文和配送中台上下文。
所以运营域呢除了运营上下文,还包含了一部分的ERP系统上下文和配送中台上下文。
所以这种特殊情况在我们的现实中其实也是比较常见的。
为什么我们需要线阶上下文呢?
因为自然语言有歧义,同一个事物,在不同场景的含义也不一样。
所以我们需要用线性上下文来对领域进行分割,控制复杂性也便于分工。
我们也介绍了划分线性上下文的三种方法,包括domestorytellingevendorming和子域概念提取。
其中呢eventstoring我们只是提到后面的章节,我们会进行详细讲解,现接上下文和微服务的关系。
微服务是线下上下文的一种实现方式。
一般一个线界上下文对应一个服务。
ok这就是我们这节课的内容,谢谢大家,我们下节课再见。

2-9 多个上下文之间如何协作?上下文映射和防腐层

大家好,上节课呢我们学习了线阶上下文的概念作用以及划分和应用的方法。
我们把smartRM系统划分成了多个线节,上下文团队分工呢也基于这种划分来进行。
接下来另一个问题就来了,谢谢。
上下文并不是孤立的,他们要互相协作,才能完成整个系统的功能。
那么划分之后的现阶上下文以及团队之间要如何进行协作呢?
怎么样去理解现阶上下文之间的关系呢?
这就是本节课,我们要学习的内容上下文映射。
本节课中,我们首先要学习什么是上下文映射,以及为什么需要上下文映射?
接下来我们要了解上下文映射有哪些已知的模式,并且深入学习各个模式的含义以及相关的案例。
那么什么是上下文映射呢?
从字面意义上来说,上下文映射是指线界上下文之间的模型映射关系。
这句话是没错的啊,等会儿我们就会知道,不过我们一般会用另外一种更直白的解释来理解它。
上下文映射描述的是团队之间的协作关系,以及上下文之间的集成关系。
说白了,就是指一个上下文是否依赖另一个上下文,如果依赖的话,被依赖的那个上下文,用什么方式提供自己的服务。
他们两个团队之间的关系怎么样,哪一个更强势一点,谁说了算,就是这个意思。
那么我们为什么要知道这些呢?
那是因为知道这些才能让我们更好的把这些上下文集成到一起,形成一个系统。
例如其中一个重要的决策点就是什么时候应该建立一个进行模型转换的代理层,我们称之为防腐层。
其实呢上节课我们通过上下文之间连线已经初步展示了他们之间的关联了。
我们至少已经知道哪些上下文之间是有关联的。
比方说交易上下文依赖,支付上下文用户上下文商品上下文设备上下文运营上下文呢依赖商品设备ERP配送中台等等等等。
所谓的依赖是指什么呢?
仅仅是指调用关系吗?
其实呢调用关系只是其中的一个方面,更重要的是模型和模型之间的映射关系。
我们来回顾一下线界,上下文划分过程中,我们进行概念抽取的这个图,不同线界上下文各自包含了一些关键概念,其中有一些重叠的概念,比如交易上下文和支付上下文都包含了支付二维码这个概念。
而交易上下文和设备上下文呢都包含了商品这个概念。
我们知道这种现象是正常的啊,因为这就是现节上下文的意义所在呀。
但是在系统实现的过程中呢,我们需要处理一个问题,就是当我们集成两个概念重叠的。
现阶上下文的时候,怎样处理这种重叠是将其中一个转换成另一个或者多个呢?
还是一个向另一个看齐,这就是我们刚才所说的模型和模型之间的映射关系。
其实它和团队之间的协作关系,以及上下文之间的集成关系是等价的。
当你要集成另一个上下文的时候,就意味着你要看到它暴露出来的一些概念。
比如说如果你要集成商品中台,那么你就要知道他传给你的商品对象,各个字段都是什么意思。
如果你的内部也有商品这个类,那么你就要把它给你的商品对象转成你内部能用的商品对象,这就是一种比较常见的映射关系。
那这个时候呢我们就称商品中台上下文是你的上游,你叫upstream你呢是他的下游叫dowstream所有这些关系呢,ericevans和wonveron把他们总结成了比较常见的九种模式,分别是合伙人共享内核客户供应商顺从者防腐层分道扬镳,也就是separateways。
那有一些是有一些书里面把它翻译成独立方法,在这里,我把它翻译成分道扬镳,其实更能体现它的意义。
还有就是开放主机服务公开语言大泥鳅。
接下来呢我们就来看看他们都是什么意思。
我们首先从一个比较容易理解的模式开始开放主机服务。
大家知道我们现在开发一个系统,很多时候会借助一些成熟的第三方服务,包括云厂商的服务或者内部中台部门以及基础架构部门提供的成熟组件。
这种服务或者组件很多时候是以公共API的方式暴露出来。
这种以公共API的形式提供服务的模式,就叫做开放主机服务。
它有两个重要特征。
首先服务提供方为所有的服务,消费方都提供一套统一的API不搞特殊化,你用就用不用也没办法,我无法为你单独提供一套AI比如作为一个客户户,很难难云厂商商你定制的API你只能用他公开的API对不对?
正因为这样呢,他只能针对通用的功能和模型,这种模式在现实中很常见,在我们的案例中也不少见。
比如我们自己的支付上下文就集成了微信支付的上下文,而微信支付提供服务的方式呢,就是开放主机服务。
那么这里呢微信支付上下文,就是upstream,也就是上游smartRM的字符。
上下文呢就是downstream,也就是下游,我们可以用简写的u和d来指代这种上下游的关系。
在开放主机服务模式中,其实还隐藏了另外一种模式。
因为开放主机服务的AII很难为下游的某一个上下文进行定制。
所以呢下游的上下文就只能遵从他提供的模型。
这时候我们称下游为conformise翻译成跟随者或者顺从者都可以。
那这个呢就是我们的另外一个模式,顺从者,模式顺从者就意味着没有模型到模型的转换一个上下文,直接引用另一个上下文的模型,顺从者模式隐藏的一个风险。
当你顺从一个上下文的时候,其实就是允许了他对你的侵入。
如果我们除了对接微信支付,还要对接支付宝或者其他的什么支付平台,也就意味着你要引入多个上下文的模型。
那么这个时候如果不采用特殊方法,你的上下文就有可能会变得比较混乱和难以维护。
在滴滴滴中呢,这种混乱的模型,我们称之为大泥球。
所谓大泥球,是指由混杂的模型构成的糟糕系统模型不稳定,而且难以维护它是一种反模式。
也就是说要避免发生的状况。
那么我们怎么样避免发生这种状况呢?
这就需要利用到另外一种很常见的模式,防腐尘。
所谓防腐层作用,就是把上下作用,就是把上游上下文的模型转换成自己上下文的模型,它是由下游上下文实现的访问外部模型的一个代理层。
在刚才我们同时集成多个支付平台的例子中,我们就可以通过设置防腐层来避免让我们自己的支付上下文变成大泥鳅。
此外呢如果我们的上下文不得已要和大泥鳅发生合作关系。
比如说要集成旧的系统,这种情况其实经常发生,主要是在对旧系统进行重构的时候。
这个时候呢,我们的上下文也很容易被大泥球污染变成另外一个大泥球,这种情况下也要积极利用防腐层来对我们自己的上下文进行保护,这是防腐层。
另外一个比较常见的应用场景。
在传统的java应用架构里,有一种比较常见的做法,就是把一些比较重要的比较内核的类,放进一个叫做什么什么call的架包里,比如把数据访问层放进这个包里,所有的服务都会去引用这个包,也可以去修改这个包。
这种模式呢,滴滴滴里也有命名,叫做共享内核模式。
所谓共享内核呢,就是指两个上下文共享部分模型共享的方式,包括但不限于代码价包,甚至动态链接库数据库表等等。
但是这里我要说的是这种模式要慎用,虽然很方便直接。
但是一旦涉及共享部分的变更,可能就会有比较大的风险。
所以我们建议简单共享的团队合作很紧密,而且共享的部分相对稳定的时候,才可以使用这种模式。
而这种合作很紧密的团队呢,他们之间的关系,我们称之为合伙人,也是一种上下文映射的模式。
合伙人模式仅仅是指一种团队的协作协作关系,仅仅是指一种团队的协作关系,跟技术无关,它是指两个上下文的团队之间可以随时互通,有无协同变更。
接下来就是我们的客户供应商模式了,这是一种让下游上下文比较舒服的模式。
下游上下文可以向上游上下文提供需求。
下游可以影响上游上下文的决策,它一般用于核心域和非核心域之间的协作。
比如我们的交易上下文运营上下文都处于核心域之中,而设备。
上下文呢是作为非核心域出现的,但是他也发挥了比较重要的作用。
现实中他有可能被交给外包团队,或者说乙方团队去负责交易,上下文和运营,上下文呢都依赖它,他们都会向设备上下文提需求而设备上下文则需要满足这些需求。
在日常开发中,客户供应商模式也是很常见的一种模式。
他和顺从者模式是一对反义词,在顺从者模式中下游,上下文是弱势的,一方,只能顺从上游,而客户供应商模式中呢下游是强势的一方。
这里我想让大家思考一个问题,如果我们和中台部门合作,我们的上下文需要集成中台的某些功能,中台是上游,我们是下游,那么我们之间的模式是什么?
模式呢?
是顺从者呢还是客户供应商模式呢?
这个问题并不好回答啊,毕竟前段时间阿里已经说过要拆中台了,其实中台一定要是相对低姿态的一方。
也就是说,这里的模式应该是客户供应商模式。
因为中台的价值在于整合各个业务相对成熟和通用的部分,并且沉淀对业务有价值的通用组件,中台的价值一定要也只能体现在业务上。
这里的业务呢就是指下游的上下文,但是中台它确实也有中台的难处。
为什么这么说呢?
刚才我们说过,当我们要顺从多个上下文的时候,我们自己的上下文就很容易变成大泥鳅。
那么如果我们要给多个上下文,当供应商会怎么样呢?
其实也是一样的,所以中台也很容易变成大泥鳅,它的模型很难稳定下来,中台的设计是非常需要智慧和技巧的,也需要具备一定的开放性。
如果中台团队不给力,最后总是要让下游当顺从者。
那么最后中台很可能会变得形同虚设下游的上下文在被逼无奈的情况下,可能会采用这种模式叫做分道扬镳,就是指两个上下文之间没有协作,各自独立设计和实现。
当两个上下文的集成成本太高的时候,就会出现这种情况。
比如刚才我们说中台如果不能满足下游的需求,下游不得不自己实现一部分再去集成中台的一部分投入产出比就显得比较高了。
那下游上下文可能就会放弃合作,自立门户。
另外呢,这种情况也比较常见于新开发的系统,对正在运行的旧系统的集成中。
如果旧系统是一个大泥鳅,集成,有可能会非常困难。
这个时候呢,新系统可能就会放弃集成,重新开始,最后流量逐步切换到新系统。
最后要讲的这个模式叫做公开语言,它总是和标准以及协议相关,是指标准中或者说协议中描述的模型,所有的上下文都可以与公开语言中的模型进行双向转换,可以转成公开语言,或者从公开语言的模型转成自己的模型和公开语言进行的这种对接的上下文呢,他们的他们互相之间呢也就可以实现组件化的对接了。
比如一个上下文集成另一个上下文,以满足某些需求,或者把自己对接的一个上下文可插拔的换成另一个上下文,这种例子也很多。
比如实现了蓝牙协议的设备,互相之间就可以通过蓝牙协议进行对接了。
蓝牙鼠标,可以对接支持蓝牙的电脑。
对接之后呢,换成另外一个蓝牙鼠标也没有问题。
同样的TCPIP协议也可以说是一种公开语言,只要实现了TCPIP设备之间就可以基于它进行相互通信了,基本上所有的标准和协议都满足这个定义。
此外呢,java生态中这种例子也很多,比如JDBC标准JVM标准等等。
如果你开发了一种类似于mysql的新数据库,只要提供JDBC包java程序,就可以像访问其他数据库那样访问你的数据库JDBC接口呢就是一种公开语言,你开发了一种新的java虚拟机,只要满足JVM标准,所有的java程序,都可以在它上面运行。
circle语言,也可以认为是一种公开语言。
不管是什么数据产品,只要支持了circle其他所有的系统或者人都可以用circle来读写里面的数据。
前面我们已经列举了smartIM系统中一些典型的上下文之间的关系。
通过类似的分析呢,我们就可以得到我们项目案例中的上下文映射的大图处于核心域的两个上下文交易,上下文和运营上下文,他们和支付上下文用户,上下文商品,上下文,还有设备上下文之间的关系,都是客户供应商模式。
外部系统包括微信支付上下文商品中台上下文配送中台上下文在为我们提供服务的时候,都是基于开放主机服务的模式ERP系统。
这里有些特殊,因为不同公司内部的ERP系统在为我们提供服务的时候,并不一定都是基于开放主机服务模式。
所以我们这里不做过多的假设。
但是我们在和这四个外部系统上下文打交道的时候,都只能扮演顺从者的角色。
有的同学可能会问,我们刚才说中台系统一定要扮演供应商的角色,这里怎么还是开放主机服务呢?
那是因为这里我们对接的中台都是存在于客户的内部,而且不同客户的情况可能也有区别。
甚至有些客户内部的系统非常老旧,已经是大泥球了。
比如说很多erp系统,我们很难要求客户为我们定制。
所以呢这里的商品中台ERP配送中台,我们都只能当他们的顺从者。
但是作为顺从者呢,我们要在自己的这一侧实现防腐层,保证我们自己的模型的纯粹不受污染。
ok我们来总结一下这节课的内容。
这节课我们理解了什么是上下文映射和为什么需要上下文映射?
上下文映射描述了上下文之间和对应团队之间的关系,本质上也是上下文之间重叠模型的映射关系。
只有清楚的这种关系,各个团队才能知道自己的定位以及怎么样更好的集成其他的上下文。
什么情况下,应该设立防腐层,上下文有九种模式,其中开放主机服务是上游用的模式,防腐层是下游用的模式,顺从者和客户供应商模式是两种相反的模式。
在这两种模式中,下游分别扮演弱势和强势的角色共享内核要慎用。
除非团队之间是合伙人,大泥鳅是一种反模式,不得已的情况下呢,不得已的情况下呢,分道扬镳,分道扬镳,也是可以采用的模式标准和协议中的模型,我们称之为公开语言对接的公开语言,就可以把上下文组件化。
在我们的案例中,我们对各个上下文的关系进行了分析,也得到了一张上下文映射的大图。
在顺从者一侧,我们设置了防护层这张大图在我们后面的设计中也会产生指导意义。

2-10 剥离领域模型与技术实现:建立分层架构

大家好,这节课我们要学习的是领域驱动设计中战略设计的另外一个重要内容分层架构,我们会学习怎么样去建立分层架构,以达到隔离领域模型的目的。
本节中呢?
首先我们要了解为什么要隔离领域模型?
接下来呢我们会对滴滴滴传统的四层架构和洋葱,或者说六边形架构进行一个讲解。
洋葱架构和六边形架构本质上是一样的,只是洋葱架构分层会更细致一些。
最后呢我们会通过案例来讲解,怎么样去建立分层架构OK那么为什么我们要建立分层架构呢?
其实我们建立分层架构的一个主要目标,就是实现领域驱动设计中的一个重要原则。
模型驱动设计。
什么是模型驱动设计?
其实前面我们已经讲到过了,简而言之,模型驱动设计就是指我们要严格遵从模型来编写代码,使得代码的设计不受到其他因素的干扰,代码和模型实现一致。
但是在现实中呢,这种原则很容易受到干扰,尤其是受到技术因素的干扰。
比如说我们考虑到数据库的性能,有可能会去创建一些领域中不存在的实体,或者说为了方便而省略掉模型层分层架构呢就能够避免模型在实现过程中被省略或者被技术因素污染。
那么分层架构。
为什么能够帮助我们达到这个目的呢?
这就是我们在本节的后面要进行详细学习的内容。
不得不说分层架构其实是一种很古老的技术传统,基本上任何一个应用程序的后台,在进行部署的时候,我们都会把它分割成网关层逻辑层和数据层。
而古老的MVC架构呢也是基于分层的思想,但是这种分层就足够了吗?
我们不妨来看一个案例,假设我们用传统的架构模式来实现我们的交易上下文。
那么代码会是一个什么样子的呢?
代码大概会是这个样子的,主要的包包括了clientcontrollerDAOentityservice和VO。
那其中呢controller不用说它是消息的入口,负责请求的接收和响应的。
返回controller呢会调用service里面的代码来进行业务逻辑的处理。
那么service呢就主要包含了业务逻辑的主体,包括了业务逻辑相关的接口和它的实现。
那么涉及到数据访问呢,会我们的DAO层主要负责数据库表的读写。
那DAO的类呢和税库表是一一对应的entity里面呢就包括了我们的设计对象实体类。
如果涉及到外部服务的访问呢,我们通过client这个包来解决密欧包里面呢主要是包括了controller要引用的一些消息类。
那么我们来看一下这样一个架构里面的业务处理流程是怎么样的。
首先消息从controller进来,假设用户在售割机上选择了一个商品,那么请求就会通过框架层由我们的tradecontroller这个类接收到。
那么这个类呢就会调用service里面的tradeservice这个类来处理这个业务逻辑,我们进去看一下。
那么tradeservice在在接收到了选择商品的请求之后呢,它会调用DAO类AO对象去数据库里面捞数据,捞到这台售卖机的商品库存数据,它会对商品的库存进行一个检查。
当库存大于零的时候,有生成订单,然后调用远程服务远程的支付服务来开始支付。
同样的,当用户完成支付之后呢,支付服务就会调用finishorder这个入口来请求完成订单。
那么controller仍然会调用tradeservice的finishorder。
那么进来之后呢,我们还是会调用DO对象来获取欧准订单的实体,并且调用售卖机的相关的服务操控服务来弹出商品。
然后呢,我们会调用商品库存的DO对象来更新库存。
最后呢会更新订单状态,并把它写到数据库里面这样一个业务处理流程,看起来中规中矩好像并没有什么问题。
但是呢很遗憾,这个看起来很常见的架构模式,就是我们要尽量避免的反模式。
为什么这么说呢?
大家不妨思考一下,这里的领域实体在哪里leendingmachineinvventory也就是我们的售卖机库存,这个类它是领域实体吗?
虽然我们把它放到entity这个包里面,但实际上它并不是领域实体所谓的领域实体。
我们在后面的课程里面会介绍到这里,我们把它理解成领域中,我们要跟踪它的状态变化的重要的业务实体,或者说业务对象就可以了。
那么weddingmachinintory也就是售数机的库存。
这里其实只是售卖机的一个数据嘛,所能作为一个业务实体来对待它。
所以其实我们是在围绕着数据在写代码所整个业务处理流程中呢,然后其实就是在不断断读数据,修改数据写数据,也就是传说中的CRUD领域模型呢,其实基本上已经被我们忽略掉了。
我们正在用的是一个所谓的评选模型。
所以最后呢我们的设计方法就会演变变,围绕的数据进行设计。
所以最接说围绕着数据库表进行设计。
这不就是我们现实中很常见的一种设计方法嘛,业务过来以后,一群人围在一起,先把数据库表结构构给录出来,然后大围绕绕结构构写代码就行了,毫无罪恶感。
最后,业务代码完全被存储存绑架,业务逻辑和技术实现混杂在一起,领域模型也被技术方案给绑架了。
最后如果我们要替换存储,或者说改动改变技术方案,可能业务代码也要进行巨大的修改。
可能有的同学会说,我们不是有做过分层吗?
你看我们有分入口层数据层服务层。
对不对?
所以我们说这样的分层是不够的,总结起来,这样的所谓的传统的架构模式有几个严重的问题。
首先,领域模型容易被省略掉,最后变成评选模型。
其次,最后设计会演变成基于数据的设计表结构决定一切。
最后呢,领域模型与技术实现混杂,领域模型也被技术实现所绑架了。
正是为了解决这些问题呢,ericevans在DDD蓝皮书中就提出了经典的市场架构。
他把系统分成了接口层应用层领域层和基础设施层四个层次,其中呢接口层主要负责外部通信,包括协议的解析封装以及消息路由等等。
应用层主要面向的是问题空间,它会指挥领域模型和基础通信来完成用户故事。
那他的工作包括像事物的控制呃事件订阅以及从资源库读写模型等等。
那领域层呢它就是核心层了,它就包含了我们领域中的领域模型,领域中的实体资源库领域事件工厂等等元素都是属于这个层。
而基础设施层呢,就主要包括一些通用的框架和工具数据库表以及存储访问等等。
那么四层架构怎么落地呢?
我们直接来看代码,还是以我们的交易上下文为例,假设我们把它重构成四层架构。
那么它的包结构就会变成这样,包括了applicationdomaininfrastructure和interface,分别就对应了四层架构里面的四个层次。
其中呢基础设施层infrastructure,它主要包含了DAO类数据对象类以及远程访问相关的一些代码。
领域层呢就是我们的核心层,它主要包含了一些领域实体。
比如说像订单呃货到售卖机以及封装的实体存储访问的资源库,包括了订单资源库和货到售卖机资源库以及资源库的具体实现。
一般呢它也包含了一些领域的服务,比如说订单服务应用层呢,目前主要包含的是应用服务,比如tradeservice这个服务接口层和之前的变化不大,主要还是包含了controller和VO类。
他负责处理消息的收发和路由。
那么让我们直接来看应用服务层。
我们可以看到应用服务层和之前相比变得非常简单,非常简洁。
比如说对于选择商品这个处理,这时候应用服务首先调用货到售卖机的资源库去获取一个货到售卖机,然后呢调用货到售卖机这个实体的一个业务方法来判断用户选择的这个商品是否处于一个可购买的状态判断通过的时候呢就会调用orderservice,也就是领域层的订单服务来创建一个订单,最后把订单添加到订单的资源库里面。
这个时候呢,应用服务已经不再像以前一样会直接去访问数据层。
比如说访问DAO类应用服务,只会去协调淋浴层的元素,包括领域实体资源库淋域服务等等的元素,让他们配合来完成用户或者是外部系统的一些请求。
而具体的业务逻辑呢就会封装到了领域层,这里出现了一个新的概念,叫做资源库。
什么是资源库呢?
我们在后面讲战术设计的时候,会对资源库进行更详细的讲解。
那这里呢我们只要理解它是一个对从存储层读写领域模型进行封装的一种模式。
从业务层看呢,资源库它就相当于是对实体的集合进行管理的一种类。
那么资源库是怎么实现的呢?
我们进到货到售卖机的方案,one这个方法里面去看一下。
我们可以看到在内部,它其实还是调用的DAO类去完成数据的读写。
但是这个时候呢,他不再仅仅是把DAO返回的数据对象直接映射到实体,而是有一个组装的过程。
比如说这里我们会先从DAO获取这个货到售卖机的库存数据,然后呢创建一个货到售卖机的实体。
根据我们刚才获取的数据来设置实体的库存,最后呢返回这个实体对象。
那么我们可以看到,通过资源库呢,其实我们就实现了从实体到表结构的一个解耦。
所以它也是分层架构里面很重要的一个工具。
ok让我们回到应用服务同样的结束,订单这个方法也变得很简洁。
首先我们从订单资源库获取一个订单,然后从货到售卖机资源库获取对应的货到售卖机,再调用售卖机的弹出商品的方法完成出货。
最后呢,调用货到售卖机的资源库来更新换了售卖机的库存。
我们可以看到,通过四层架构的重构,我们实现的最大优化,就是我们的设计和编码,不再围绕着表结构去进行了。
那么领域模型就可以变得相对独立,它不再被技术方案所绑架。
而开发和设计人员呢也能变得更聚焦。
它可以把精力更多的放在领域模型的建立和实现上,也就更能够接近我们的目标模型驱动设计。
所以总结下来,滴滴滴经典的四层架构,它解决的是什么问题呢?
首先,它能够分离关注点,在这个四层架构里面,每一个层它的职责都变得更清晰,也更聚焦。
其次,领域模型变得更独立,受业务无关因素的影响更小了。
最后呢这个四层架构它自带单向依赖的属性。
但是呢传统的DDD四层架构在某种程度上来说,它也存在着一些缺陷。
什么缺陷呢?
那就是领域层对基础设施层仍然是有感知的,领域层的实现仍然是强依赖于基础设施层的,它和技术实现仍然是耦合的。
比如我们看一下我们的资源库的实现。
在这种架构下,我们资源库的实现仍然包含了大量的数据库表,读写相关的代码。
虽然领域层对外表现的更干净了,但是领域层的实现仍然是依赖于基础设施层的领域模型和技术实现并没有实现完全的解耦。
那么要解决这个问题,最好的办法呢,其实是把资源库的实现放到基础设施层。
然后再把它装配到领域层。
那么这种方式呢,其实就是我们所说的依赖倒置,也就是这个图描述的架构。
那么基于这种思想呢,后来就发展出来了,六边形架构和洋葱架构。
刚才我们说过这两种架构本质上是一样的。
它的核心思想就是把领域模型放到了核心层领域模型变得极其的纯粹和独立。
那么围绕它的呢就是一些适配器,这些适配器的作用呢,就是完成领域模型和外部系统,它的数据交换,包括收发消息和读写存储等等。
那么如果我们把交易上下文,按照洋葱架构来进行重构,会怎么样呢?
在这种架构下,我们把交易上下文的实现分成了四个包,包括适配器adapterapplication应用层领域层和基础设施层,其中呢应用层没有太大的区别。
但是呢我们把领域层的资源库的实现放到了适配器层,也就是adapt包里面,而李玉存只保留了它的接口。
因为本质上资源库的实现完成的是领域模型和数据存储层之间的数据交换。
此外呢我们把原本基础设施层中的远程服务,调用相关的代码拆成两部分,把接口放到了领域层,而实现呢也放到了层,也就是适配器层。
为什么我们要这么做呢?
因为本质上我们调用其他上下文的外部服务,其实就是对上下文映射的一种实现。
这种服务调用其实就代表了从我们上下文看其他上下文的模型是什么样子的。
所以本质上它也是我们的领域模型和其他上下文的领域模型,它的一个数据的交换。
我们要在这种adapadapt中完成领域模型的转换,包括防腐层也可以实现在里面。
除此之外呢,基础设施上也没有其他的变化。
那么适配器层呢,它仍然要实现消息的收发和路由包含controller和VO。
此外呢也要实现刚才我们所提到的外部服务调用的adapter和资源库的实现。
经过这种重构呢,领域模型就会变得非常干净解决。
刚才我们提到的问题,领域模型非常独立,不再受到业务无关因素的影响,这种代码结构呢就忠实履行了洋葱架构。
所以我们来总结一下洋葱架构的优势。
首先,它能够保持领域层的纯粹性,不受其他任何因素的干扰,它便于践行模型驱动设计,使得代码跟随模型。
最后呢它也有利于团队把精力集中到领域模型上OK让我们来对本节的内容做一个简单的总结。
首先我们学习了,为什么要建立分层架构?
这是因为分层架构有利于我们实现领域驱动设计的一个重要目标。
模型驱动设计,使得代码跟随模型。
我们详细的讲解了四层架构和洋葱,或者说六边形架构,也了解了它的实现方法。
最后我们基于洋葱架构对我们smartRM系统中的交易上下文建立了分层架构。
我们通过案例学习了怎么样去建立分层架构?
这里需要说明的是,本节涉及到的案例架构,它都是为知识点服务的。
其中忽略了一些因素,包括它的错误处理以及它的模型,也不是最终我们要用到的模型,在后面的战术设计的学习中,我们还会对这些代码进行最终的完善,最终的代码呢也会和现在我们讲到的代码有比较大的区别。
ok这就是本节的内容,谢谢大家。

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

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

相关文章

【Node-RED】node-red-contrib-opcua-server模块使用(2)

这里写自定义目录标题 前言示例简单介绍变量产生opcuaServe配置 地址空间的配置创建opcua服务器获取命名空间初始化变量定义文件夹定义文件夹中的变量view文件夹增加view文件夹中查阅信息定义最终效果 加密设置opcuaServe组件配置客户端配置 参考官网博文 前言 上期博文【Node…

零基础学编程,中文编程入门视频教程

零基础学编程,中文编程入门视频教程 一、前言 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载——常用工具下载——编程工具免费版下载及实例源码下载。 如上图为编程工具构件…

c语言实战之贪吃蛇

文章目录 前言效果展示游戏用到的图片游戏思路一览游戏前准备一、贪吃蛇、食物、障碍物节点坐标的结构体二、枚举游戏状态、和贪吃蛇的方向三、维护运行的结构体 游戏开始前的初始化一、学习图形库相关知识二、设置背景三、欢迎界面四、初始化贪吃蛇五、生成障碍物六、生成食物…

Codeforces Round 811 (Div. 3)(VP-15,寒假加训)

A. 模拟 // Problem: A. Everyone Loves to Sleep // Contest: Codeforces - Codeforces Round 811 (Div. 3) // URL: https://codeforces.com/group/RAx5fiilkP/contest/1714/problem/A // Memory Limit: 256 MB // Time Limit: 2000 ms // // Powered by CP Editor (https…

SharedPreferences卡顿分析

SP的使用及存在的问题 SharedPreferences(以下简称SP)是Android本地存储的一种方式,是以key-value的形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里,SP的使用示例及源码解析参见:Android本地存储之SharedPreferences源码解析。以…

【JAVA语言-第16话】集合框架(三)——Set、HashSet、LinkedHashSet、TreeSet集合的详细解析

目录 Set集合 1.1 概述 1.2 特点 1.3 HashSet集合 1.3.1 概述 1.3.2 哈希表 1.3.3 哈希值 1.3.4 练习 1.3.5 HashSet存储自定义类型元素 1.4 LinkedHashSet集合 1.4.1 概述 1.4.2 特点 1.4.3 练习 1.5 TreeSet集合 1.5.1 概述 1.5.2 练习 1.6 HashSet、Lin…

Pandas展开数据

def testExplode():df = pd.DataFrame({key: [A, B],data: [[1

【论文复现】

code: paper: 论文 介绍 方法 实验 结论 复现 Image generation 问题1:No models "dcface/dcface/pretrained_models/adaface_ir101_webface4m.ckpt Traceback (most recent call last):File "/data/dcface/dcface/src/r…

线性回归需要满足的几个假设

线性回归模型是基于一些假设构建的,这些假设有助于确保模型的有效性和可解释性。以下是线性回归需要满足的几个主要假设: 线性关系假设(Linearity): 线性回归假设因变量(目标变量)与自变量(特征…

如何通俗解释Docker是什么?

要想弄懂Docker,咱们得先从“容器化”讲起。 一、容器化技术及Docker的出现 容器化,它是一种轻量级、可移植的软件打包方式,你就想象成一个快递箱子,里面装着你的应用和所有需要运行的环境,这个箱子能在任何支持容器…

Redisson分布式锁介绍及实战应用(防止缓存击穿)

本地锁 浏览器把100w请求由网关随机往下传,在集群情况下,每台服务都放行10w请求过来,这时候每台服务都用的是本地锁是跨JVM的, 列如这些服务都没有49企业,此时有几个服务进行回原了打击在DB上面,那后期把这…

Blender教程(基础)-物体的移动、旋转与缩放-04

一、新建一个立方体 ShiftA新建一个立方体用来演示。 二、物体的移动 xyz轴移动 点击下图图左侧的移动选项后,选中要移动的物体,会出现三个箭头的方向,这分别代表沿着x、y、z轴移动。xyz平面移动 这个小正方体代表沿着某一个面移动&#…

AWS 专题学习 P14 (Security Encryption)

文章目录 专题总览为什么需要加密?AWS KMS(密钥管理服务)KMS 密钥类型AWS KMS(密钥管理服务)Copying Snapshots across regionsKMS Key Policies在不同账户之间复制快照KMS Multi-Region Keys (多区域密钥)DynamoDB 全…

ElasticSearch 学习笔记

基本概念 术语 文档(document):每条记录就是一个文档,会以 JSON 格式进行存储 映射(mapping):索引中文档字段的约束信息,类似 RDBMS 中的表结构约束(schema&#xff09…

在linux上进行编译调试

1.相关疑问 1. 为什么在代码里使用了一个未定义过的函数(如add()),在编译阶段不会报错,在链接阶段会报错呢? 答:先说几个代码编译的结论: 单个\.c源文件文件被编译成机器码文件时&#xff0c…

LVS 工作模式

1、LVS DR模式 DR 模式是通过改写请求报文的目标 MAC 地址,将请求发给真实服务器的,而真实服务器响应后的处理结果直接返回给客户端用户。DR 模式可以极大的提高集群系统的伸缩性。但是要求调度器 LB 与真实服务器 RS 都有一块网卡连接到同一物理网段上…

Codeforces Round 785 C. Palindrome Basis

C. Palindrome Basis 题意 定义一个正整数 a a a 是回文的(没有前导 0 0 0)当且仅当: a a a 的十进制表示形式回文 给定一个正整数 n n n ,求出将 n n n 拆分成若干个回文数之和的方案数 思路 这是一个经典模型&#xff0…

媒体邀约:怎么吸引总体目标受众?

新闻媒体影响力日益扩大。不论是公司、机构还是其他,都希望能够通过新闻媒体的曝光来吸引更多总体目标受众。要想真正吸引住总体目标受众并非易事,需要一定的方案和方法。下面我们就深入探究媒体邀约推广的真相,共享怎么吸引总体目标受众的方…

秋招面试—计算机网络安全

2021 计算机网络安全 1.Get 和 Post 的区别 get 用于获取数据,post用于提交数据; get 的缓存保存在浏览器和web服务器日志中; get 使用明文传输,post请求保存在请求体中; get 长度限制在2048以内 2.常见的HTTP请…

Android P 屏保和休眠相关知识

Android P添加屏保功能,如果休眠时间设定大于屏保时间,则先进入屏保,达到休眠时间后再进入休眠 需求: 添加屏幕互保开关,默认关闭。只保留时钟,可设定指针和数字、夜间模式。启用时间改多长时间无操作进入…