Appearance
全局配置
Appearance
模型是对领域的抽象和模拟。
建模是针对特定的问题建立领域的合理模型。
模型它一定是针对特定问题,特定领域的模型,例如商品、订单、库存这几个概念在电商平台系统里面和在超市的进销存系统里面,他们的模型是完全不一样的。
商品 在电商平台里面,商品属性,商品标签,商品图片,它可能都是商品非常重要的组成部分。因为客户需要对商品进行挑选。
但是在超市的进销存系统里面,就完全不一样了。因为我们进超市买东西都是现场挑选的,其他几个商品的属性,图片、标签这些概念可能就相对没有那么重要了。
但是我们拿到商品之后,给店员进行扫码,然后生成订单,我们要对商品订单进行支付,超市要对库存进行实时的修改。即商品条码这个概念对超市的进销存系统来说是比较重要的。
订单 订单这个概念在两个系统里面也完全是不一样的。在电商系统里面的订单就是类似于一种合约。订单经过了复杂的生产配送环节之后,商品才能最终到达用户的手上。
但是在超市的进销存系统里面,订单只是支付、购买的凭据 和商品销售事件的记录而已,订单的处理也相对比较简单。所以通过这个例子,我们可以看到,确实同样的事物在面对不同领域不同问题的时候,它的模型是不一样的,建模一定是针对特定问题的建模。
复杂性来源于业务本身的复杂性和设计引入的额外复杂性
业务复杂导致模型复杂
技术实现引入额外复杂性
领域驱动设计通过分解模型和模型驱动设计控制复杂性
INFO
我们一直在实践中探寻更好的软件开发方法, 身体力行的同时也帮助他人。由此我们建立了如下价值观:
也就是说,尽管右项有其价值, 我们更重视左项的价值。
我们遵循以下原则:
我们最重要的目标,是通过持续不断地及早交付有价值的软件使客户满意。
欣然面对需求变化,即使在开发后期也一样。
为了客户的竞争优势,敏捷过程掌控变化。
经常地交付可工作的软件,相隔几星期或一两个月,倾向于采取较短的周期。
业务人员和开发人员必须相互合作,项目中的每一天都不例外。
激发个体的斗志,以他们为核心搭建项目。提供所需的环境和支援,辅以信任,从而达成目标。
不论团队内外,传递信息效果最好效率也最高的方式是面对面的交谈。
可工作的软件是进度的首要度量标准。
敏捷过程倡导可持续开发。责任人、开发人员和用户要能够共同维持其步调稳定延续。
坚持不懈地追求技术卓越和良好设计,敏捷能力由此增强。
以简洁为本,它是极力减少不必要工作量的艺术。
最好的架构、需求和设计出自自组织团队。
团队定期地反思如何能提高成效,并依此调整自身的举止表现。
Domain-Driven Design:Tackling Complexity in the Heart of Software 《领域驱动设计:软件核心复杂性应对之道》Eric Evans 2004
Implementing Domain-Driven Design《实现领域驱动设计》Vernon Vaughn 2013
因为零售行业本质上并不是一门流量的生意,而流量也就是用户相关的部分,只是其中一小部分。整个零售概括起来需要解决的问题包括三个方面的问题,也就是人、货、场三方面的问题。
人指的是用户,货指的是商品,场是指交易完成的场所,包括各种形式的卖场、商超、平台等等。
其中,人和场是和用户体验相关的,货和厂是和供应链相关的,而供应链才是零售中最重要的问题。
道理很简单,大家买东西最主要看的是什么呢?最主要看的还是性价比,对不对?零售企业的供应链能力才能决定性价比。
目前电商最擅长的是还是一个人和场。也就是说电商最擅长的还是用户体验。所以电商占零售大盘的比例想超过50%是非常困难的。
所以京东从来不说自己是一家电商公司,而称自己为供应链公司。作为电商巨头的阿里,也提出来新零售这个概念,作为腾讯则提出了自己的智慧零售这个概念。
虽然有差别,但是想做的事情是类似的,也就是用于大数据AI和物联网相关的技术去影响零售中的人、货、场,从而对零售的供应链和用户体验进行优化。
他们都希望通过智慧零售或者新零售的概念,把自己的价值带到零售行业完整的价值链,而不仅仅限于用户侧。
而传统的零售公司也希望借助技术来优化自己的供应链的质量和协调率,同时优化用户体验,把用户粘在自己的平台上,而不用依赖于传统的电商渠道。
所以从这点上来说,传统的零售公司和新兴的互联网技术公司,对于智慧零售和新零售的诉求是一拍即合的。所以智慧零售和经济的零售就成了零售行业后面发展的一个大势所趋。
无人零售是新零售和智慧零售中一个重要的方向。我们的案例也是面向无人零售相关领域的。我们要开发的是一个面向自动售卖机的这么一个零售SaaS系统。
其中既包括了面向用户的交易部分,也包括了面向企业所有的运营人员的供应链管理部分。
这个系统连接了用户、零售业务运营人员和企业的内部系统。
它既要负责管理和操控自动售卖机,并且提供交易系统,让用户在售卖机上完成购物,又要负责帮助运营人员完成商品的选品和库存管理,实现运营利润的最大化。
同时又要连接客户的内部系统,从而充分利用客户现有的内部资源和流程。客户的运营人员可以在这个系统上决定在哪个地方投放售卖机,而商务和安装人员会通过企业ERP系统收到投放申请,最终由安装人员完成设备安装。
安装好之后,运营人员会在我们的SaaS上,针对已上线的设备,选择要进行售卖的商品,并采用一定的策略,使得商品能够及时的补货,始终处于一个可以购买的状态。
这些策略会被我们的系统转化成配送申请,发送给企业内部的配送系统,最终由企业的配送员完成商品的配送。
可以看到,在这个领域中,客户企业现有的内部系统也会深度参与其中,包括企业内部已有的ERP系统、商品中台、配送中台等等。可能不同业务系统的架构有所区别。但是大体上一个成熟的零售公司会有这几部分的系统支撑。
其实现在大多数的业务系统都会面临这么一个情况,一方面会有自己的创新部分,一方面就会与外部系统和历史系统进行交互,充分利用资源。案例也会模拟这么一个情况,来看DDD是怎么应对这种情况的。
假设我们面向的客户就是这么一个比较成熟的零售企业。这个企业想引入自动售卖机,作为新的业务形态。而我们的系统实现的就是基于自动售卖机的零售系统,包括交易和运营,我们称这个系统为 SmartRM ,即 smart retail machine 的简写。
A公司:SaaS公司,主要面向零售企业客户
SmartRM产品团队:一位资深产品经理,几位产品策划
SmartRM研发团队:一位架构师,10+开发,资深开发3至4位
B集团:零售行业某头部商家
全国各地有大量商超和便利店,有成熟的内部系统和供应链
基于自动售卖机的零售业务是新业务
复杂度可覆盖DDD大部分知识点并体现其价值
智慧零售市场庞大且场景接近日常生活
有数据分析需求,可结合大数据分析场景
这四个阶段虽然开始时间有先后,但并不是上一个阶段结束,下一个阶段才开始,而是重叠进行的。
对用户故事的挖掘和建立,通用语言是最先开始的,也可能会贯穿整个建模设计的过程。同样战术设计的开始,也并不意味着战略设计的结束。
在战术设计的过程中,我们有可能会发现战略设计的不合理,从而回过头来对战略设计进行优化。
同时,在战略设计和战术设计中,我们也会不断的对通用语言进行丰富,甚至发现业务流程中的优化点,从而回过头来重新研究用户故事。
所以这里我们也要提到Eric Evans曾经提出的建模涡流这个概念。什么是建模物流呢?就是指两种活动交替完善,最终得到一个合理的模型。
其实在建模过程中,这种涡流有不少,刚才我们提到的战略设计和战术设计的交替完善,就是其中一种。
举个例子说,我们在战略策略中会将模型分解成限界上下文,但是随着建模的深入,更多的实体会发现有,可能我们会发现有必要分割出新的上下文。
那么对战略设计进行修改,以及对通用语言进行补充和完善都是没有问题的。而且在这种情况下,也是必须要做的。
再举一个更细粒度的例子:在战略设计的内部,我们要先对领域进行划分,然后再寻找限界上下文,理想情况下,子领域域和限界上下文是一一对应的。但是有可能在寻找限界上下文的过程中,我们会发现一个子领域下划分出了两个限界上下文。这就说明之前的领域划分是存在问题的,需要进行调整,而调整之后的领域划分也能更好的指导限界上下文的划分,从而形成一个良性循环。这就是另外一个涡流。
所以说建模设计过程并不是一个那么规整的过程,它是以形成最优模型为目标的一个不断优化的过程。
团队里一般会有3种角色,领域专家、研发团队以及产品团队,其中领域专家是非常重要的角色。
领域专家是指在某个领域有深度经验的专业人士。当我们要做一个外卖系统的时候,外卖员就是领域专家。但我们要做一个医疗系统的时候,医生就是领域专家。但是在现实中,很多情况是产品经理来担当一个专家的角色,这也是可以的。但是必要的时候需要邀请相关行业的业内人士参与讨论。
整个建模和设计流程的第一步,要从用户故事开始,在敏捷软件开发中,用户故事是一种对软件系统特性的非正式的描述,是从终端用户的角度对问题的描绘。所以我们说它是对问题空间的描绘。
很多团队在做产品的时候,一上来产品经理先出一个无比细致的设计文稿,各种流程图,界面一应俱全。开发同学也很喜欢用写需求文档不细致这一点来怼产品。其实软件产品开发并不是按部就班的搬砖再牛的产品经理,他也做不到。一开始就确定产品的最终方案。如果他在各方压力,尤其是来自开发者的压力下,真的尝试去这么去做,一开始就把产品从功能到界面全部定稿,并且保证以后永不再改。那么最后出来的产品很可能是没有生命力的产品。好的产品它一定是经过充分的讨论,反复修改、打磨得到的。当然经过讨论之后得到的最终方案,他还是要写清楚的,那些好的产品文档,也可以作为通用语言文档。
所以基于这一点,敏捷方法,提倡在早期用户故事来对需求进行恰到好处的描述,不会太过细致。
用户故事更多的对问题的描述,而不是解决方案。
通过从问题出发,通过整个团队的讨论论证,包括云专家、研发、产品经理之间的充分讨论,不断递进,最终接近答案,也就是最合理的产品方案。用户故事一般类似上面这种描述方式。
作为什么人,我希望做什么,从而达到一个什么样的目的。
比方说在自动售卖机上通过手机扫码支付购物,这个用户故事都可以这样进行描述:
作为用户,我希望通过手机支付在自动售卖机上购买商品,从而能够更方便快速的购物。
通过文字对他进行描述之后,还要把团队内部相关人员聚在一起进行讨论。比如售卖机扫码支付购物,这个就可以把相关的产品经理、研发,必要的时候,还可以包括售卖机的运维人员或者技术支持人员聚在一起进行讨论。一边讨论一边画图,这个图也可以是更简单的ER图(Entity-Relationship diagram 是一种用来描述实体Entity之间关系的图形化工具,常用于数据库设计中)。通过这种图就可以快速的把讨论的结果固化下来。
上面这个图是对电影购票的这个场景描述:比如第一步,顾客向售票员请求预留座位。第二步,售票员去票务系统上从放映计划上查找空闲座位。第三步,售票员向顾客推荐座位等等。
可以看到通过这种过程,我们慢慢从问题空间进入方法空间了,也就是逐步发现顾客和售票员可以怎么样通过票务系统来解决买票这个问题的。
通过讨论画这个图的过程,又叫做domain story telling。(后面讲通用语言的时候会详细介绍)
这就是我们挖掘用户过渡的过程。除了domain story telling 也可以用其他的一些建模方法,如事件风暴来对用户故事进行挖掘。
在讨论模型和定义模型时,团队使用的同一种语言。
领域知识需要在团队内部高效流转,模型需要描述。
通用语言要体现在代码里
通用语言是DDD里面非常重要的概念。所谓通用语言,就是指在讨论和定义模型时,团队使用的同一种语言。
为什么要有通用语言呢?答案很明显,因为首先领域专家脑子里的知识需要传达给软件开发人员。
其次,建模得到的模型需要进行描述和记录,而且对模型的描述,领域专家也要能够看懂,以便进行反馈。
最后,通用语言也会体现在代码里面。代码中的类名属性名、方法名都要尽可能的使用通用语言中出现过的词汇进行命名。
其实在对用户故事进行挖掘的时候,我们就已经开始在建立通用语言了。在讨论的时候,大部分时间是领域专家在讲解,其他人要做的是理解领域专家提到的各种概念,及时提问,同时把它们固化下来。
打个比方说,如果我们在讨论自动售卖机,团队里面有的人说售卖机,有的人说贩卖机,那么可以提出来,我们以后统一称它为售卖机。甚至把出现过的关键词汇提炼成一个表格。
例如上图我们对案例中货道售卖机扫码支付购物这个用户故事进行挖掘得到的图。
我们把这个图里出现的名词提炼出来,放到表格里,中英文都固定下来。在后面的战略设计和战术设计的过程中,我们还可以不断对通用语言进行调整和补充。所以通用语言基本上是贯穿于整个过程的。
这张图,外面的虚线圈代表真实世界,里面的实线圈代表一个完美的领域模型,它可以很好的描述一个领域。旁边的虚线圈表示团队正在讨论的一个并不完美的领域模型。
平时说的自然语言,它是用于描述真实世界的,所以它指向真实世界。而团队使用的领域通用语言则是指向的模型,所以我们说通用语言是描述模型的。
最初通用语言指向的并不是一个完美的模型。但是我们还是要使用它,因为只有这样模型的不完美才会被暴露出来。
团队的所有成员在一起使用通用语言的时候,会发现它和自己的理解不一致,要么是理解出了问题,要么是模型的问题。
经过仔细辨别之后,如果是个人理解出现问题,个人理解就得到了纠正。如果发现是领域模型的问题,我们这就会给你模型进行优化,从而使讨论中的模型逐渐接近理想模型。
但是如果团队成员使用自然语言,而不是基于模型的通用语言进行交流,我们可能始终都无法发现我们对业务理解的问题或者模型本身的问题。这就是为什么通用语言一定要是基于模型的语言。
类和操作的名称
施加于模型之上的规则和约束
应用于领域模型的模式
一般来说,通用语言包括我们模型里类和操作名称。
例如我们在对售卖机扫码支付购物进行storytelling时,提取出来的商品、支付二维码以及货道售卖机弹出商品中的弹出等等。这些词就是通用语言的内容,他们可以作为类名和操作名。
此外通用语言也包括在模型上施加的规则和约束。
比如当初卖这种商品卖完了要给他补充商品时,补充的商品数量不能超过售卖机的容量。这就是规则和约束。也是属于通用语言中的内容。
最后应用于模型上的模式。
比如工厂、仓库等等,甚至包括限界上下文这种词汇也是通用语言的一部分。
DDD中对问题空间和解决方案空间进行分解的过程叫做战略设计
战略设计目的是分解模型以控制复杂性
战略设计是DDD与传统建模和设计方法的核心区别之一
上面这张图就是战略设计的输出。
战略设计主要做三件事情
确定上下文映射过程其实还会决定在哪里加入防腐层
对各个BC的细节设计过程
BC内部的模型结构与完整技术方案
这个图是从领域驱动设计这本书截下来的。这些元素就是战略设计相关的主要结构元素,包括聚合、实体、值对象、工厂、仓库、模块、服务等等。由这些元素构成的结构确定下来之后,代码基本上也就确定了。战术设计包括编码,设计人员或者架构师是需要深度参与编码的。
最简单易用,和用例分析法类似,基本没有区别,领域故事陈述法更加成熟
最主流方法
稍复杂,偏冷门,不是专门为DDD提出
建模一定要从问题空间出发,而且通用语言也需要尽早开始建立。因为无论是描述模型还是团队沟通,都需要依赖通用语言。
在正式开始战略,从用户故事开始对领域进行探索,可以使得开发人员能够迅速学习领域知识,并且在团队内部初步建立通用语言,为后面的领域划分寻找限界上下文乃至寻找聚合根等等战略战术设计工作,做好必要的铺垫。而且业内有观点也认为通用语言的建立就是战略设计的一部分。
与其代码写好后修改需求,不如不要拍脑袋定需求。
与其说需求文档不细致,关键点没有?不如基于用户故事讨论。
在软件开发中,用户故事是一种对软件系统特性的非正式的自然语言描述,是敏捷软件开发中从终端用户的角度对软件系统特性进行捕捉的一种方式。用户故事描述了不同类型的用户需要什么以及为什么需要,它可以帮助我们创建需求的简单描述。
在软件开发和演进过程中,随着产品和开发对产品认识的加深,需求总是在不断变化,所以,过早地进入需求细节以及对细节的描述,是一种时间上的巨大浪费。从这一点来说,用户故事提供了一种恰到好处的粒度,使得产品在需求分析阶段能够极大地节约时间,并且使产品和研发人员始终把注意力集中在关键点,避免他们过早地陷入细节以及被细节所局限,同时给产品功能留出了讨论空间,从而使产品有机会在讨论过程中得到优化。
用户故事的构建一般来说有三个环节:
分别对应用户故事的三个元素,也就是3C:Card(卡片)、Conversation(谈话)、Confirmation(验证)。
“卡片”就是指对用户故事的简述(传统上人们通过便利贴在白板上构建用户故事),一个好的用户故事卡片包括三个要素:
谁:谁需要这个功能;
需要什么:想通过系统完成什么事情;
为什么:为什么需要这个功能,这个功能带来什么样的价值。
谈话是指用户、领域专家、产品经理、研发之间围绕用户故事进行的讨论,谈话是明确需求细节的必要环节。可以用文字对谈话进行简要记录,此外,也可以基于图形或其他工具进行讨论。
验证代表了验收测试,描述了客户或者产品owner怎样确定用户故事已经被实现,且能够满足需求。一般可以用如下模板写Confirmation:
假设我是<角色>,在xxx情况下,
当我<操作>,
那么<结果>。
当SmartRM立项之后,我们开天辟地要做的第一件事情,就是梳理出最顶层的用户故事。在团队中,撰写用户故事的一般是产品经理。在SmartRM案例中,我们的产品经理梳理出来以下这些最主要的顶层用户故事,迈开了系统分析和建模工作的第一步,为我们的后续工作打下了基础。(所有用户故事的详细描述,都是建立通用语言的关键资料,因此,我们将其收录进SmartRM通用语言文档)
售卖机扫码支付购物
卡片
作为用户,
我希望在售卖机上通过手机扫码支付购买商品,
以便快速便捷地购物。
谈话
验收标准
假设我是一名用户,货道售卖机屏幕的商品列表上有商品A, B, C,
当我在售卖机屏幕上选择了商品A,并扫描展示的二维码完成支付后,
那么商品A就会从售卖机中弹出,我可以拿到商品A。
柜门机免密购物
卡片
作为用户,
我希望在支持自动结算免密支付的柜门机上扫码开门后拿到商品,关门就可以结束购物,系统后台可以自动结算并且扣费,
以便在柜门机上能更快捷地进行购物。
谈话
验收标准
假设我是一名用户,柜门机里有商品A, B, C
当我打开柜门,从柜门机拿走商品A并且关闭柜门,
那么后台会自动完成结算和支付,从用户账户扣除商品A的价格,并且锁定柜门。
其他顶层用户故事还包括:
售卖机投放
售卖机撤销
补货
经营分析
此处我们仅对用户故事的描述方法进行示例,所有用户故事的详细描述,请参见 SmartRM通用语言文档
学会一种语言的最好方式是听别人说这种语言,尝试重复他们所说的并留意他们的反馈。
Domain Storytelling是一种领域分析建模方法,通过这种方法,产品和研发人员可以利用语言学习的相同原理建立或者学习一个领域的通用语言,并且建立领域模型。
在storytelling过程中,一方(通常是架构师)聆听另一方(通常是产品经理或者领域专家)以主谓宾的句型(谁做了什么)讲述用户故事的流程,进行问答和讨论,并以图形的方式快速复述出来。
使用在线工具domain-story-modeler,可以在线实践domain storytelling。
顶层用户故事的storytelling描述了顶层用户故事的整体框架,但是其中对部分活动的描述并不够,为了更深入的了解领域,我们对这些部分可以进一步展开,进行下一层用户故事的storytelling。例如这里我们对“用户选择商品”(图中01)、“货道售卖机弹出支付二维码”(图中02)两个子用户故事进一步进行了storytelling。
为了充分建立通用语言并且使我们接下来要进行的战略设计能够精准合理,我们对所有顶层用户故事以及必要的子用户故事进行了storytelling,由于domain storytelling是建立通用语言之前的重要环节,是建立通用语言的有效手段之一,因此我们也将其结果完整收录进了 SmartRM通用语言文档,其它几个顶层用户故事(售卖机投放、售卖机撤销、补货、经营分析)及其子用户故事的storytelling结果具体请参见该文档。
我们对用户故事进行storytelling,其实就是建立领域通用语言的过程,storytelling的输出结果(上述的storytelling图)也就包含了领域通用语言的完整语句,对象、角色、活动、以及体现它们相互作用的完整语句,在图中都可以一览无余。这里我们可以更进一步,将通用语言中的词汇提炼出来,将其中英文都列在通用语言词汇表中,这些词汇将会贯穿整个建模和设计过程,最终也会体现在代码中,因此团队中所有成员,都需要明确理解其含义,并且在相关讨论、模型、以及代码中使用它们。这个表格的样例如下所示,完整表格较长,具体请参见 SmartRM通用语言文档
类型 | 中文 | 英文 | 上下文 |
---|---|---|---|
对象 | 商品 | commodity | 用户选择商品 |
对象 | 支付 | payment | 用户完成支付 |
对象 | 货道售卖机 | slot vending machine | 货道售卖机展示支付二维码 |
对象 | 支付二维码 | payment QR code | 货道售卖机展示支付二维码 |
系统 | 手机支付平台 | mobile payment platform | 用户在手机支付平台完成支付 |
对象 | 柜门机(无人货柜、货柜机) | cabinet vending machine | 柜门机解锁柜门 |
对象 | 柜门机二维码 | cabinet QR code | 用户扫描柜门机二维码 |
.... | .... | .... | .... |
领域划分是对问题空间的划分,原则是分离关注点,目的是将系统的建模和设计工作分而治之,进行简化,并为寻找限界上下文提供依据,打下基础。
用户故事是从用户的角度对需求的描述,也就是领域问题的描述,是与实现方案无关的。我们可以借助用户故事进行领域划分。顶层用户故事能够让我们对系统有一个全局性的认识,但是并不包含太多细节,其中的每个活动,可能较复杂,也可能包含了多个子领域的信息。如果我们要深入利用用户故事进行战略和战术设计,需要将它分解成子用户故事,将用户故事中模糊的部分逐层分解,直至清晰和聚焦。
对于分解之后的用户故事,我们可以按照分离关注点的原则将其进行分类,将关注同一细分领域的用户故事分为一组,将这些用户故事形成的小组命名,即可得到各个子领域,同时根据用例之间的层次关系,也能得到各个子领域之间的关系。
通过分别从各子领域的用户故事中提取关键概念,审视它们之间的关系,以及它们与外部系统之间的关系,我们可以梳理出系统中的限界上下文。如下图所示:
融合了领域划分和限界上下文的全局系统概览如下图所示:
确定上下文之间的关系,绘制上下文映射图,是战略设计中非常重要的一步。
通过明确SmartRM之间的关系,决定应用到上下文之间的协作模式,并且在必要的地方引入防腐层(ACL)。我们得到下面的上下文映射图:
What is User Story? (visual-paradigm.com)
User Stories | Examples and Template | Atlassian
售卖机扫码支付购物
卡片
作为用户,
我希望在售卖机上通过手机扫码支付购买商品,
以便快速便捷地购买商品。
验收标准
假设我是一名用户,货道售卖机屏幕的商品列表上有商品A, B, C,
当我在售卖机屏幕上选择了商品A,并扫描弹出的二维码完成支付后,
那么商品A就会从售卖机中弹出,我可以拿到商品A。
谈话
柜门机免密购物
卡片
作为用户,
我希望在支持自动结算免密支付的柜门机上扫码开门后拿到商品,关门就可以结束购物,系统后台可以自动结算并且扣费。
以便在柜门机上能更快捷地进行购物。
验收标准
假设我是一名用户,柜门机里有商品A, B, C
当我打开柜门,从柜门机拿走商品A并且关闭柜门,
那么后台会自动完成结算和支付,从用户账户扣除商品A的价格,并且锁定柜门。
谈话
售卖机投放
卡片
作为运营人员,
我希望按照我的要求把特定型号的售卖机投放到指定的点位,
以便通过售卖机销售商品产生利润。
验收标准
假设我是一名运营人员,
当我在系统上指定投放地点和型号并确认投放,
那么投放申请会发送给企业的ERP系统,在ERP系统的实施流程界面可以看到该申请及其状态。
谈话
售卖机撤销
卡片
作为运营人员,
我希望按照我的要求撤销指定的售卖机,
以便节约成本,提升整体利润
验收标准
假设我是一名运营人员,
当我在系统中申请撤销指定的售卖机,
那么系统会发送售卖机撤销申请到企业的ERP系统,在ERP系统的实施流程界面可以看到该申请及其状态。
谈话
补货
卡片
作为运营人员,
我希望按照特定的运营策略,对售卖机中的商品进行及时补充,
以便售卖机中的商品始终处于可购买状态。
验收标准
假设我是一名运营人员,
当我在系统中提交了售卖机A的库存计划,设定了它的商品B最大库存是X,安全库存是Y,
那么系统会按照我的库存计划生成配送申请并发送到配送中台系统,售卖机A中的商品B库存低于Y时,第二天配送人员会对售卖机A中的商品B进行补货,使其库存达到X。
谈话
经营分析
卡片
作为运营人员,
我希望能及时查看经营分析报表,
以便确定和调整运营策略,包括决定和调整库存计划,决定投放和撤销售卖机的等等。
验收标准
假设我是一名运营人员
当我从系统中选择指定查看点位X的销售报表时,
那么相关界面会弹出,并且通过图表的方式展示该点位的销售指标。
谈话
顶层用户故事的storytelling描述了顶层用户故事的整体框架,但是其中对部分活动的描述并不够,为了更深入的了解领域,我们对这些部分可以进一步展开,进行下一层用户故事的storytelling。例如这里我们对“用户选择商品”(图中01)、“货道售卖机弹出支付二维码”(图中02)两个子用户故事进一步进行了storytelling。
类型 | 中文 | 英文 | 上下文 |
---|---|---|---|
角色 | 用户 | user | 用户选择商品 |
活动 | 选择 | select | 用户选择商品 |
对象 | 商品 | sku | 用户选择商品 |
活动 | 完成 | finish | 用户完成支付 |
对象 | 支付 | payment | 用户完成支付 |
对象 | 货道售卖机 | slot vending machine | 货道售卖机弹出支付二维码 |
活动 | 展示 | display | 货道售卖机展示支付二维码 |
对象 | 支付二维码 | payment QR code | 货道售卖机弹出支付二维码 |
活动 | 弹出 | pop out | 货道售卖机弹出商品 |
系统 | 手机支付平台 | mobile payment platform | 用户在手机支付平台完成支付 |
活动 | 扫描 | scan | 用户扫描支付二维码 |
对象 | 柜门机二维码 | cabinet QR code | 用户扫描柜门机二维码 |
对象 | 柜门机 | cabinet vending machine | 柜门机解锁柜门 |
活动 | 解锁 | unlock | 柜门机解锁柜门 |
活动 | 锁定 | lock | 柜门机锁定柜门 |
对象 | 柜门 | cabinet door | 柜门机锁定柜门 |
对象 | 订单 | order | 柜门机订单结算 |
活动 | 结算 | settle | 柜门机订单结算 |
活动 | 授权 | authorize | 用户授权免密支付 |
对象 | 免密支付 | password-free payment | 用户授权免密支付 |
活动 | 拿取 | take | 用户拿取商品 |
.... | .... | .... | .... |
P: 用户在设备屏幕上选择商品后,设备展示支付二维码,用户使用微信扫描二维码,完成支付后,设备完成出货,交易结束,设备屏幕上回到商品列表界面。
D: 这里的设备是指自动售卖机吗?
P: 是的。
D: 那么我们以后统一用”售卖机“这个词吧?英文用Vending Machine。
P: 没问题。
D: 如果用户支付失败,会怎么样?
P: 售卖机会等待一段时间,然后取消交易,回到商品列表界面。
D: 售卖机出货会失败吗?
P: 有可能,不过我们还是要找懂这套售卖机的人了解下。
D: 是的,我把运营人员O拉进来聊下。
P: O,我们在对SmartRM系统进行建模,想咨询一些售卖机相关的问题。售卖机出货会失败吗?
O:我们应用的售卖机主要包括自动称重式柜门机、弹簧货道售卖机、蛇形货道售卖机。其中,称重式柜门机特点是售卖的商品类型多,不会卡货,主要用于办公楼、商超、小区等室内外公共场所的饮料、零食、生鲜等商品的售卖;弹簧货道售卖机的特点是成本低,售卖商品类型多,卡货概率高,主要用于室内外等公共场所的饮料、零食的售卖;蛇形货道售卖机的特点是体积一般较大、库存容量大、卡货概率低、省电,但是只能用于饮料的售卖,主要放在体育场、工厂、学校、公园等室内或室外公共场所用于饮料售卖。目前我们有一部分存量弹簧货道售卖机,后面大部分室内的会替换成自动称重柜门机,但是仍然会保留一部分。我们的售卖机可靠性很优秀,但是还是会有几种出货失败的情况。比较常见的出货失败的情况有以下几种:1) 卡货;2) 售卖机网络问题;3) 库存错误
P:柜门机的购物流程是这样的:
P:当我们的运营人员发现新的点位并与点位的相关管理人员达成初步意向时,可以在SmartRM系统提交售卖机的投放申请。SmartRM会把申请转发到商家的企业ERP,接下来会经过商务沟通、合同签订、部署安装等环节,在这个流程中,运营人员可以随时查看售卖机投放的进展和状态。
D: 投放成功后我们应该做什么?
P: 投放成功后,运营人员可以正常地管理这台售卖机,为它制定补货计划。并且查看它的运营效率。
D: 投放有可能失败吗?
P: 一般不会,因为我们的商务人员会提前与点位的控制者达成一致,当然,如果合同签订环节出现无法解决的问题,则法务人员会在ERP中终止流程,此外,合同签订以前,运营人员也可以随时在SmartRM系统中终止或暂停申请。
P: 当我们发现售卖机无法盈利时,需要把它撤销。
D: 如何确定一台售卖机无法实现盈利呢?
P: 我们从售卖机的经营数据可以看出端倪。但是,现在先让我们把售卖机撤销这件事情的流程讲清楚吧。
P: 当我们决定撤销售卖机时,我们在SmartRM系统提交撤销申请,SmartRM系统会提交相应申请到ERP系统,此后,同样会经历商务沟通、合同解除、设备回收等环节,流程中,运营人员同样可以随时查看售卖机撤销的进展和状态。
D: 撤销成功后,还需要在系统中看到它吗?
P: 不需要了。
D: 撤销有可能失败吗?
P: 不可能。
P: 库存计划是最重要的运营策略之一,制定库存计划的最终目标是最大化整体运营利润,整体运营利润由销售毛利润减去运营成本得到,因此补货计划需要尽量增大每台售卖机的销售毛利润,并且尽可能减少平均单机运营成本。其中,单机运营成本的配送成本是可以通过优化补货频率、补货路线来降低的。我们的配送人员,每个人会负责固定的一批售卖机,人员成本会均摊到每台售卖机,而售卖机的补货频率、补货路线,会影响每个人能够负责的售卖机数量。
D: 所以运营人员制定库存计划的过程是怎么样的?会希望从系统获得哪些帮助呢?
P: 对于新上线的售卖机,运营人员会根据点位的特征和售卖机的型号,使用模板生成库存计划;
P: 对于已有的售卖机,运营人员会查看商品销售报表,调整不同商品的计划库存比例,使得不同商品的补货周期趋于一致,这样可以使得单台售卖机的补货频率最小化。
P: 当然,最重要的是,我们要确保商品的销售保持在最佳水平,也就是说让每台售卖机销售最热卖的商品。这一点,我们可以从商家现有的商品中台获得帮助,商品中台保存了商家现存的所有可销售商品,以及不同商品的销售数据、商品画像等信息。商品中台提供公共API,外部系统可以通过指定搜索词、特征等参数获取销售最好的商品。当我们发现售卖机现有库存计划中的某个商品销售情况不好时,可以选择新的商品代替它。
D: 运营人员需要分别针对每台售卖机按上述过程操作吗?
P: 事实上,运营人员需要决定的是每台售卖机的库存计划,补货计划最好是能由系统自动生成。而当库存计划偏离上述目标时,系统应该能够提示运营人员进行调整。
P: 我们会分析决定不同售卖机销售的商品种类和数量,以确定库存计划。
D: 所以我们需要针对每台售卖机从商品池里选择适合销售的商品是吗?
P:是的,我们的目标是选择最优的商品搭配,以使得运营利润最大化。
D:运营利润的定义是什么呢?
P:运营利润=毛利润-运营成本;毛利润=商品销售收入-商品进价成本
D:我们怎么知道一台售卖机的运营成本和毛利润是多少呢?
P:运营成本由很多因素构成,主要包含售卖机的硬件成本(折旧均摊到每个月)、点位租金、电费、维修成本等等,我们会从商家的企业ERP系统直接获得每台售卖机的运营成本。运营收入目前只包含商品的销售收入,我们的系统可以自己根据订单数据得到,同时ERP系统里也会根据财务数据得到一份,在系统无误的情况下,两边的数据应该是一致的,但是我们也需要每个月进行对账。
领域划分是以分离关注点为原则对问题空间的划分
子域是领域中某个方面的问题和解决它所涉及的一切
产品经理梳理出来了六大核心顶层用户故事,分别是售卖机、扫码支付、客户柜门基金、免疫购售卖机、投放售卖机、撤销捕获和进行分析。
我们前面说过,用户故事就是对问题的描述。这六个用户故事,他们所描述的问题是可以进行划分或分类的。
很显然,前面两个,售卖机扫码支付、购物购物免密购物解决的是交易问题。后面四个,售卖机投放、售卖机撤销、补货、经营分析,解决的是运营问题。他们的功能区分非常明显,面向的用户角色不一样,操作入口也不一样。
我们首先就可以把smart IN领域划分成这两个大的子域交易域和运营他们一个解决交易问题,一个解决运营问题。这样我们其实就已经进行了初步的领域划分了。
思考:这样的划分是不是就已经足够了呢?领域划分要到什么力度才算合理呢?为什么要进行划分?
基于领域划分进行分工协作而非基于需求
在现实中,有一种比较常见的开发流程和分工模式,这种模式是这样的:
首先产品经理提需求,写文档,接下来开发leader把需求分配到不同的开发同学进行技术方案设计代码。
假设我们把产品梳理出来的六个顶层用户是逐个分配给六个不同的开发同学会出现什么状况呢?(请注意,在项目时间压力比较大,而研发leader又没有做太多思考的情况下,这种分工是很可能出现的)
看起来好像并发度很高,大家同步开发,但这种分工协作方式有两个致命问题。
先来看第一个问题点和领域知识重叠。我们以售卖机扫码支付购物和柜门机免密购物这两个用户故事为例。这两个用户故事如果深入进去,会发现他们涉及很多的问题。其中包括这么三个问题,一是支付,二是交易流程,三是设备操控。
假设这两个用户故事被分配给了两组不同的开发,这两组同学就要同时分别关注这三个方面的问题,压力很大不说,而且还重复劳动了。大家知道同时做几件事情是很难做好的,更何况还有重叠的工作,效率低不说,可能还有冲突。
再来看第二个问题,不仅关注的问题和领域知识重叠了,模型也重叠了。
从这两个用户故事分别分析下去,都会提炼出来几个关键的概念:商品、订单、售卖机。这些概念最终都会转化成代码。那么这两组同学又会出现重复劳动和冲突。最后这几个概念对应的类的设计到底谁说了算?这两个问题最终导致的后果,一是效率低,二是职责重叠导致冲突。
解决他们的方法就是子域划分。
我们把这两个用户是涉及的问题,细分后写几个子域,其中就包括了支付域、交易域和设备域。不同子域交给不同的人来负责。
而商品、订单、售卖机这些个概念最终会落入不同的子域。限界上下文和子域是紧密相关的。在理想的设计下,子域和限界上下文是一一对应的。
这样的划分以后,问题就迎刃而解了。不同的开发同学面向的是不同的问题,他们工作更聚焦了,也没有重复劳动和工作的问题了。
基于用户故事分解可以让领域划分清晰化
在架构师本人能力比较强、经验比较丰富的情况下,直接通过直觉和经验去进行一个划分也是OK的。而且其实软件系统的设计也有它艺术性的一面,优美的划分本身就是解决方案的一部分领域划分也是能体现架构师的能力的。
但是那就没有一种能对领域划分有指导和辅助性作用的固定方法吗?毕竟技术人员的能力的强化是需要时间的,而且架构师的工作也最好能有一种方法进行检验,最好能保证结果的准确性和效果的稳定性。
刚才其实我们已经透露过一点,领域划分是对问题空间的划分。而碰巧用户故事也是对问题的描述。那么是否可以借助用户故事对领域进行划分呢?
接下来我要介绍一种我们在实践中检验过行之有效的方法,叫做基于用户故事分解的领域划分,它利用的就是用户和领域划分的相关性。
上面是使用的domain storytelling分析售卖机扫码支付购物这个用户故事得到的图。
之前我们说到storytelling有很多意义,比如熟悉领域知识,建立通用语言,甚至建立模型。其实他也可以认为是对用户故事的分解。每个序号代表的隐喻可以认为是一个子用户故事。
比如用户在货道售卖机上选择商品,货道售卖机展示支付二维码,用户扫描支付二维码,其实都是子用户故事,他们的有序进行构成了外层的用户故事。这些子用户故事有一些非常具体,具备了原子性,已经可以直接落地,不能再拆了,甚至对应到代码里面就是一行代码。有一些仍然包含了大量的细节,比较复杂。对于这些复杂的用户故事,是可以进一步进行分解。
比如货道售卖机展示支付二维码,这个子用户故事就包含了较多细节,我们可以对它进行进一步的分解,也就是说对它再做一次storytelling, 得到了下面这个图。
货道售卖机首先需要向smartRM系统后台请求支付URL, 请求里当然会包含商品信息。
smartRM系统后台收到请求后,根据商品信息生成订单,然后根据微信的支付流程需要去微信支付平台获取code_url,也就是预支付交易链接,最后获得售卖机生成的二维码,展示二维码。这样的分解可以一直进行下去。
那么分解到什么程度才能停下来呢?对于领域划分来说,当分解之后的所有子用户故事已经足够聚焦,聚焦到请关注一个很小的一个问题,甚至变成了一个原子操作。那么分解就可以停止下来了。
最后我们得到一个树状图,树状的节点,就是用户故事。他们可以看成是一个个或大或小的,需要我们正在进行建模和设计的软件系统去解决的领域问题。
当然当分解进入到叶子节点时,问题和答案的界限就已经变得比较模糊了。这个过程非常类似敏捷软件开发里常用的分解方法,叫做story mapping。
我们对交易域里的两个用户故事进行这种story mapping得到了两棵树。
现在我们看到这个数对应的是售卖机扫码支付购物。
接下来的事情就显而易见了,我们需要对这些故事进行分类。我们按照关注点对他们进行聚类。
先来看一下售卖机,扫码支付购物这棵树。
“用户选择商品”这个节点和它的子用户故事,可以认为解决的都是交易问题。 “货道售卖机展示支付二维码”,看起来有点像解决的支付问题,但是其实它这里展示的是所选商品的支付二维码,它还包含了根据所选商品生成订单的操作,所以他解决的仍然是交易过程中的问题。他的子用户故事包括“生成订单”、“货道售卖机请求支付URL”、“获取code_url”、“生成支付二维码”、“展示支付二维码”。这里这个内层的展示支付二维码和它外层父用户故事的相比,仅仅是UI展示,其实已经不属于领域问题了。
其中“生成订单”解决的交易问题,而其他的可以认为解决的支付问题。当然除了“展示支付二维码”,这个已经分解到不属于领域问题用户故事。 “货到付款机弹出商品”、“检测故障”、“上报故障”,很显然解决的都是设备操控的问题。
“取消订单”本身解决的是交易问题,但是它包含的退款,这个只用户是解决了支付问题。
同样的,我们对柜门机免密购物对应的这棵树也可以进行这样的归类。
我们把每个类别的节点分别串起来,给每个类别起个名字,分别是交易域、支付域、设备域和用户域(柜门机免密购物分解后,有用户注册和用户登录的操作,我们把它归到用户域里)。
通过用户故事之间的层次关系,我们又可以得到各个子域的引用关系,或者也可以说是依赖关系、集成关系。
交易域需要引用支付域设备域和用户域。
就这样通过故事分解,我们把原先的交易域分解成了更合理的四个子域。
同样的我们对所有顶层的用户故事都可以进行这样的解剖和归类,最终可以得到smartRM系统全局的领域划分方案。
通过对运营域剩下的四个顶层用户故事的剖析,我们发现还需要独立出来一个子域,叫做商品域。它包含了“商品信息获取”和“运营挑选商品”,也就是选品相关的内容。最终我们得到了六个子域,他们分别是“交易域”、“支付域”、“用户域”、“设备域”、“运营域”和“商品域”。我们也同步得到了他们之间的依赖关系。
所以我们通过故事分解,得到了这样一个整体的划分。划分之后得到的子域都有了特定的含义和明确的职责。
其中交易域负责实现面向终端用户的商品交易。用户购买商品时使用的设备前端界面、完成交易的后台程序都是在这个域里。
设备域负责实现售卖机设备的管理和操作。他会将售卖机设备抽象成合理的售卖机模型,并且通过API对设备进行远程操控。我们的设备会上云有可能是阿里云,也有可能是AWS或者是其他。它们不同型号可能会有自己的物模型,这些细节对我们这个领域并没有那么重要。最终经过领域专家和我们的开发人员在这个子领域中的工作,其他子域看到的是售卖机的领域模型。这个模型只包含了我们在smartRM系统中真正关心的部分。
支付域,负责是在线扫码支付、免密支付等支付相关的功能。在柜门机免密购物的流程中,用户需要注册和登录,因为系统需要分辨他们的身份,记录他们的状态和交易相关的信息。比如说在扣款过程中,需要知道他们的账号,那这些就交给用户运营去做。
运营域负责帮助运营人员制定最优的运营策略,并且将它们落地。
除此之外,无论是交易域还是运营域,都需要和商品打交道,他们需要获得的商品详情,运营领域需要进行商品搜索、商品选品等等,这些都是在商品域完成。
以上就是我们基于用户故事分解进行领域划分的整个过程。
注意,系统设计仍然有它的艺术性,同一个问题分解的方式可以不一样。实际上分解本身就是从问题空间向方案空间进行的过程,分解方案就是解决方案的一部分。
我们提供的方法只是一种辅助分析的方法,这种方法是有效的,但它不是机械的操作流程。分解的过程中仍然需要大量的思考和团队的讨论。不同的分解方式会导致不同的方案和效率。好的分解才能有好的方案。
战略设计要明确核心域,团队尽量减少非核心域投入。
从个人发展角度,程序员也要尽量投入核心域的工作。
核心域指的是系统中代表的产品的核心竞争力部分。
如果你的产品要在市场上生存,那么你在核心域上的表现就需要比竞争对手强。
如果一个子域不是核心域,但是有很多子域都要依赖这个子域,那么它就是通用子域。
如果它既不是核心域,也不是通用域,那么它就是支撑域。
其实对我们来说,最重要的是把核心域从系统的其他部分区分出来,过多纠结概念的意义其实不大。DDD强调把核心域显示的区分出来是为了聚焦。
很多团队里的技术大牛其实都不太喜欢做业务开发。但是我们认为这个技术含量,而是喜欢把多数精力都投入到底层。比如说框架层的开发,很多同学投入精力在这一块,包括甚至有一些业务开发的团队也分出了一部分精力量去开发了一个类似的。
对于技术人员来说,追求技术深度是绝对有必要的。但是DDD认为,对于项目和产品来说,最核心的技术人员应该把精力投入到核心域的设计和开发里面,尽量减少团队在其他部分的投入,这样才能高效的提升产品和业务的核心竞争力,并且使公司的利益和个人发展形成良性循环。
程序员到底应该做基础架构,还是做业务架构?做基础架构能够提升个人的技术能力,而做业务架构能体现个人在业务中的价值,更容易提升个人技巧,获得在公司内部的晋升机会。
到底应该如何选择呢?关键点并非是从事什么技术方向,任何技术都有过时的一天,但是任何领域的核心域的氛围都是相对稳定的。
对于零售来说,核心域是供应链。对于游戏来说,核心域是游戏的核心玩法。对于广告来说,核心域必须围绕着提升转化率。对于云来说,核心域就是基础设施了。
当你身处于一个领域中时,你只有做核心域才能得到更多的发展机会,增强个人的竞争力,同时兼顾个人和公司的利益。
所以如果你想从事基础架构相关的工作,倒推一下,应该去云厂商或者公司内部的技术架构部门工作。因为在那个领域里面,基础架构才是核心域,而不要选择在业务开发团队里做技术架构的工作。
如果你从事的是业务开发,则应该更可能的深入业务的核心域,把精力放在解决最重要的问题上。而在这个过程中,你仍然可以发现打磨技术的机会。因为公司和公司、产品和产品之间在核心域的竞争仍然会不断的对技术提出越来越高的要求。
核心域的建立总是伴随着精炼,精炼有两种方法。
精炼就是不断的提炼和压缩的过程。
通过精炼,我们分离出领域中普通的部分,最终得到领域中极致精华的核心域。
在这个核心域中的每点滴的工作,都会产生最大的价值,而且不受系统中其他部分的干扰。精炼可以让团队用尽可能小的代价换取最大的成功概率。那么怎么样去精炼呢?其实和我们日常生活中的经验相似,方法有两种。
一种叫萃取,就是随着我们对领域分析的深入,我们逐渐发现最能影响产品成功的关键问题是什么,从而把它单独提取出来。类似把咖啡从咖啡豆里萃取出来。
另一种叫分离,就是随着我们对问题的分解,我们逐渐剥离出来,对领域的核心问题影响不大的部分,那剩下的部分就成为了更为精华的核心域。类似用筛子把沙子从金沙里过滤掉,只剩下金子。
从smartRM的交易域分解出来支付域、设备域和用户域,其实就是精炼的过程。剩下的交易域它就是系统的核心域之一了。
从smartRM系统划分出来的子域,哪些是核心域呢?对smartRM系统来说,最重要的问题,一个是怎么样去保证用户的体验,使得用户愿意在我们的系统中购物。另一个就是怎么样帮助运营实现运营利润的最大化。
核心域就是交易域和运营域。其他的子域,要么是通用子域,要么是支撑子域。
区分所谓的通用子域和支撑子域并没有那么重要。如果一定要区分,可以认为这里的商品域和设备域是通用子域,支付域和用户域是支撑子域。
限界上下文是在解决方案空间对模型的分解单位。
限界上下文是一种语义上的上下文边界。意思是在这个边界里的软件模型组件都有它特定的含义并且做特定的事。一个限界上下文内的组件都是上下文特定的并且语义明确的。-- Vaughn Vernon《领域驱动设计精粹》
一张简单的人民币钞票,在面对不同场景、不同领域时,可能可以用很简单的模型来描述,也可能需要用非常复杂的模型来描述它。其实就是因为面向不同问题,或者说不同场景,不同领域时,它所展现出来的语义是不一样的。同样的在我们的案例smart IM系统中也有这样的例子。
比如同一台售卖机,在运营人员眼里和在用户眼里是一样的。在运营人员眼里,他是一个库存不断被消耗的,又需要及时进行补充的商品容器。而在用户眼里,它是一台吞钱土商品的机器。
所以当运营人员和用户在他们各自的场景,或者说上下文里提到售卖机这个词的时候,他的意思其实是有差别的。如果我们要让这个词具备单一的含义,单一的作用,那就需要明确指出来它的界限上下文是什么?这就是界限上下文的本质。
甲: “你这是什么意思?”乙:“没什么意思,一点小意思”
INFO
首先运营需要决定在什么地方,放置什么型号的售卖机。我们把这个动作叫做投放,投放到的那个地点,我们叫做点位,点位是有很多特性的,比如名称、坐标、标签等等。
所谓标签就是这个点位有什么特性,周围的年轻人多呢,还是老年人多,是运动场所还是学校等等。
投放过去的售卖机,包括货道售卖机,以及我们所说的柜门机,其实就是自动货柜机或者称货柜机。只要在项目里,大家用词统一就可以了。
接下来运营需要决定在售卖机里面放什么商品,以及放多少和什么时候对售卖机里的商品进行补充。
按照领域专家的说法,运营需要制定库存计划,库存计划里面包含了商品组合,每种商品的最大库存和安全库存。所谓安全库存,就是当商品库存低于多少时,就需要对它进行补充了。
库存计划会每天定时执行,执行之后会生成配送订单,发送到配送中台。这里的配送中台是指客户的配送中台。对我们来说就是外部系统了。
然后配送平台会把配送订单分配到配送员,配送员再根据配送订单把商品配送到指定的售卖机。最后用户才能在售卖机里购买到商品。
商品和售卖机这两个概念在这个图里有出现过多次,首先看售卖机,运营投放售卖机,配送人员配送商品的售卖机,用户在售卖机上购买商品。
运营、用户、配送员他们眼里看到的售卖机是不一样的。运营看到的是容纳和消耗商品库存的容器,用户看到的是收钱土商品的机器,而配送员则主要关注的是收货地址和操作。售卖机就像是一个收货有点麻烦的收件人。
同样的,他们眼里的商品也不太一样。所以这里至少有三个限界上下文,分别是运营上下文、交易上下文和配送上下文。
这和我们前面划分出来的子域并不是一一对应的,缺少包括支付上下文、用户上下文、设备上下文和商品上下文,这些上下文并没有消失,而是隐藏在更细节的模型里面。在讨论和绘制系统的大图时,往往颗粒度是比较粗的。
其中很多活动需要深挖下去,比如用户购买商品这个活动如果展开,我们就会发现用户上下文、支付上下文以及设备上下文运营
制定库存计划这个活动,会发现商品上下文。
又比如说配送上下文,对应的配送域是存在的,但是它并不在我们的系统。在立项的时候就已经很清晰的了解到了配送问题,是依赖客户的配送中台来解决的,我们不会投入任何精力在它上面,所以配送域在系统的外部,我们在领域划分里也就没有把它列出来。
这里在划分界面上下文的时候,是需要把它找出来的。因为我们需要集成,需要明确内部线上效果的和他的关系。
其实如果我们把运营投放售卖机这个活动展开的话,还会发现另外一个隐藏的现象,叫做ERP系统上下文,它代表客户内部的ERP系统,我们通过它来执行售卖机的投放订单,最终完成售卖机的安装。这个是通过Domain Storytelling划分限界上下文的方法。
从库存计划到配送订单的这个箭头
也就是我们刚才用的特征。需要注意的是,如果我们需要通过语义区别去划分线下效果,那我们在进行讨论和画图的时候,就要注意要用不同的图标来代表不同含义的概念。比如如果售卖机和售卖机不一样,我们就要用两个图标来代替它
在我们的案例中,库存计划的执行是定期触发,和售卖机的投放是不同的触发方式。那么他们是不是应该在不同的限界上下文中呢?
这里我们提到的边界的标志不是绝对的,有时候三个标志都是对的,也并不表明边界就一定存在。而有时候即使一个标志都没有,我们也可以考虑加上一条边界。
在项目前期的战略设计环节,对系统进行线上销售额的划分,建议是宁缺毋滥。比如这里我们的确可以在售卖机投放和库存计划这里设置一条边界,把运营上下文划分成两部分。
但是这里存在一种可能,那就是售卖机的投放和库存计划的制定其实是紧密相关的。因为本质上售卖这个投放解决的也是库存问题。如果我们过早的把它们划分成两个不同的项目,交给不同的团队之后,会不会丧失一些在后续深入设计的过程中,本来有可能发现的全局优化的机会呢?如果他们的确属于不同的上下文,那么我们迟早会发现。根据建模涡流,调整战略设计的机会并不会关闭。
Domain storytelling是一种比较新的建模方法,提出来的时间大约是在2018年前后,目前的影响力还不太大。相比之下event storming事件风暴法可以说是DDD中最重要的建模方法。
这里我们单独提一下商品上下文和商品中台上下文。在零售系统里面商品是非常重要的概念。在我们的系统中,多个子域都会依赖到商品域。我们在领域划分时,虽然知道商品信息获取和选品相关的问题可以借助客户的商品中台,但是仍然保留了商品域这个子域,因为我们并不能确定客户的商品中台是否能完全满足我们的需求,而且不同客户内部的商品不太一样,所以我们需要保留自己的商品上下文,对外部的商品上下文做一个转换,然后提供给内部的其他上下文。
融合了领域划分和限界上下文的全局系统概览如下图所示:
微服务是限界上下文的一种实现方式,一般一个限界上下文对应一个服务。
INFO
微服务架构的反面是单体架构,在单体架构里面,不同服务他们往往是共用网关和数据层,多个服务往往也挤在一个应用里面然后一起部署,虽然单体架构也可以弹性扩缩容,但是不同服务是在一起的。
在微服务时代,我们倡导的是每一个服务都有自己独立的体系,独立的网关、独立的数据层,独立的部署节点。所以我们可以看到,微服务架构其实主要是关于隔离性的。但是微服务的推行主要依赖强大的基础设施,否则会变成灾难。
不管是单体架构还是微服务架构,都可以实现限界上下文的一。所以微服务只是限界上下文的一种实现方式。一般来说一个限界上下文对应一个服务,这就是微服务和限界上下文的一的关系。
上下文映射是指限界上下文之间的模型映射关系
描述团队之间的协作关系以及上下文之间的集成关系。
决定上下文之间如何集成以及如何设置防腐层
就是指一个上下文是否依赖另一个上下文。如果依赖的话,被依赖的那个上下文用什么方式提供自己的服务。它们两个团队之间的关系怎么样?哪一个更强势一点,谁说了算,等等。
知道这些才能让我们更好的把这些上下文集成到一起,形成一个系统。
例如其中一个重要的决策点就是什么时候应该建立一个模型转换的代理层,我们称之为防腐层。
INFO
我们至少已经知道哪些上下文之间是有关联的。比方说交易上下文依赖支付上下文、用户上下文、商品上下文、设备上下文。运营上下文依赖商品上下文、设备上下文、ERP上下文、配送中台上下文等等。
所谓的依赖是什么呢?不仅仅是调用关系,调用关系只是其中的一个方面。更重要的是,模型和模型之间的映射关系。
回顾限界上下文的划分过程中进行概念抽取的这个不同限界上下文各自包含了一些关键概念,其中有一些重叠的概念,比如交易上下文和支付上下文都包含了支付二维码这个概念。交易上下文和设备上下文都包含了商品这个概念。
在系统实施的过程中,当我们集成两个概念重叠的线性参考的时候,如何处理这种重叠呢?是将其中一个转换成另一个或者多个呢?还是一个向另一个看齐?这就是我们刚才所说的模型和模型之间的映射关系。其实他和团队之间的协作关系以及上下文之间的集成关系是等价的。
当你要集成另一个上下文的时候,就意味着你要看到它暴露出来的一些概念。
比如说如果你要集成商品中台,那么你就要知道他传给你的商品对象各个字段什么意思。如果你的内部也有三个类,那么你就要把它给你的商品对象转成你内部能用的商品对象,这就是一种比较常见的映射关系。
这个时候我们就称商品中台上下文(被依赖方)是你的上游upstream,你(依赖方)是它的下游叫downstream。
由混杂的模型构成的糟糕系统,模型不稳定且难于维护(要避免出现这种情况)
与大泥球合作的上下文要确保自身不被污染,设置防腐层
两个上下文共享部分模型
包括但不限于代码、jar包、。so、数据库表等等
慎用,仅当团队紧密合作且共享部分稳定
技术无关,是一种团队协作关系
两个团队之间可以随时互通有无,协同变更
标准化与协议化的模型
所有上下文都可以与公开语言中的模型进行转换
对接了公开语言的上下文之间可以实现组件化对接
蓝牙协议、tcp/ip
Java生态的jidbc、jvm标准等
SQL
建立分层架构的一个主要目标就是实现领域驱动设计中的一个重要原则 -- 模型驱动设计。
模型驱动设计是指我们要严格遵从模型来编写代码,使得代码的设计不受到其他因素的干扰,代码和模型实现一致。
但是在现实中,这种原则很容易受到干扰,尤其是受到技术因素的干扰。
比如说我们考虑到数据库的性能,有可能会去创建一些领域中不存在的实体,或者说为了方便而省略掉模型层。
分层架构就能够避免模型在实现过程中被省略,或者被进行一种污染。
严格按照领域模型来编写代码
建模和实现中都有破坏该原则的因素
架构分层能够避免模型在实现过程中被省略或者污染
领域模型易被省略,变成贫血模型
容易演变成基于数据的设计,一切从表结构开始
领域模型与技术实现混杂,易被技术实现绑架
其中接口层主要负责外部通信,包括协议的解析、封装以及消息路由等等。
应用层主要面向的是问题空间,它会指挥领域模型和基础设施来完成用户故事。包括像事物的控制、事件订阅,以及从资源库读写模型等等。
领域层它就是核心层了,它就包含了我们领域中的领域模型,领域中的实体、资源库、领域事件、工厂等等。
而基础设施层就主要包括一些通用的框架和工具,数据库表以及存储访问等等。
分离关注点
让领域模型层更独立
单向依赖
领域层对基础设施层仍然有感知,领域模型和技术实现耦合
洋葱架构和四层架构这两种架构本质上是一样的。洋葱架构的核心思想就是把领域模型放到了核心层,领域模型变得极其的纯粹和独立。围绕它的是一些适配器。这些适配器的作用就是完成领域模型和外部系统的数据交换,包括收发消息和读写存储等等。
保持领域层的纯粹性,不受其他因素干扰
便于践行模型驱动设计,代码跟随模型
便于把团队精力集中到领域模型
语言 | Java |
---|---|
IDE | IntelliJ IDEA |
Jdk | Java SE Development Kit 8 |
依赖管理和构建 | Maven 3 |
应用开发框架 | Spring boot 2.5.4 |
关系型数据库访问ORM | Mybatis 3.5.6 |
通用工具库 | Guava 30.1.1-jre |
定时任务调度 | Quratz 2.3.2 |
对象含义 | 所处层 | 业内常用命名 | 阿里规范 |
---|---|---|---|
视图层对象 | 接口(适配层) | VO | VO(能省则省) |
数据传输对象 | 应用层 | DTO | DTO |
数据存储对象 | 基础设施 | PO(Persistent Object) | DO(Data Object) |
领域对象 | 领域层 | DO(domain object) | BO(business object) |
生成后下载文件解压用IDEA打开文件夹
java/com/smartrm/smartrmmonolith
├── SmartrmMonolithApplication.java
├── commodity 商品上下文
├── device 设备上下文
├── infracore 全局共享基础设施包(共享内核集成)
├── operation 运营上下文
├── payment 支付上下文
├── trade 交易上下文
└── user 用户上下文
java/com/smartrm/smartrmmonolith
├── SmartrmMonolithApplication.java
├── commodity
│ ├── adapter 适配层
│ ├── application 应用层
│ ├── domain 领域层
│ └── infrastructure 基础设施层
├── device
│ ├── adapter
│ ├── application
│ ├── domain
│ └── infrastructure
├── infracore
│ ├── aggregate
│ ├── api
│ ├── common
│ ├── event
│ ├── exception
│ ├── idgenerator
│ ├── scheduler
│ └── security
├── operation
│ ├── adapter
│ ├── domain
│ └── infrastructure
├── payment
│ ├── adapter
│ ├── application
│ ├── domain
│ └── infrastructure
├── trade
│ ├── adapter
│ ├── application
│ ├── domain
│ └── infrastructure
└── user
├── adapter
├── application
├── domain
└── infrastructure
货道售卖机购物流程
无人货柜机(柜门机)购物流程