DDD落地实践-架构师眼中的餐厅(转)

本文以餐厅场景为叙事主线,以领域驱动为核心思想,结合架构设计与功能设计方法论。是从领域分析到落地的全过程案例,内容偏重于落地,因此不乏一些探讨,欢迎指正。

文章较长、全程干货、耐心读完、必有收获。

本文不针对餐厅的实现细节,重在探讨设计思想和方法。



1、领域设计

让我们抛开技术人员的本能技术视角、站在纯业务视角来分析领域问题。

领域设计的核心是分而治之,目的是实现业务领域的自治性。

就像你平时不会将枕头和被子放在厨房或卫生间一样,你的床上不会放着大米白面,否则你想睡觉是一件很复杂的事情,软件系统也是如此,这就是我们要解决的问题。



1.1 宏观流程

假如我要设计一个餐厅,由于分而治之的需要,我会首先从宏观流程去分析,可以帮我们迅速找到重要的区域。





因此会得到几个明确的行为区域,我将餐厅划分为“菜品域”,“订单域”,“厨房域”,“用餐域”,这是业务级别的领域划分,后续应该针对每个区域单独分析。

产出物是:宏观流程和参与角色



1.2 统一语言

语言贯穿于整个开发过程,从需求分析到设计、从设计到编码,因此好的语言非常重要,好的语言体现了清晰的业务概念。

在这个阶段,我们需要通过梳理,找到业务中都有哪些实体与行为,对其做一些归纳。我们的核心问题是:“谁”通过什么“行为”影响了“谁”,其中的三个要素分别是:角色、行为、实体。 我的建议是先找到 “角色”、“实体”、“行为”,并对其归类,我常常关注角色以及具体身份、实体以及实体实例,功能以及包含的重要步骤。

角色:是施事主语、是名词,是主动发起行为的一类实体。

行为:是动词、是做了什么事情,是行为本身。

实体:是名词,是除“角色”之外的其他实体。

推荐使用脑图画出来,我认为归纳后的脑图有助于我们识别根本要素,有利于抽象。

产出物是:名词、概念定义、相关脑图。







1.3 用例分析

在这一步、我们使用相对宏观的分析,不需要进入用例的细节分析,掌握角色与行为之间的关系,理清谁在做什么,角色的职责差异是什么。

产出物:用例图

以做菜为例,如图







1.4 领域划分

我们在分析宏观流程时,划分了几个行为区域,但那是业务级别的。在那基础之上,我们需要拉进某个区域的视角,再结合之前的用例分析,按照“功能相关性”、“角色相关性”进一步划分领域。

功能相关性:是用例与领域之间的关系,任何业务的领域都是由一套用例组成的,所以领域划分以功能相关性为主,例如与做菜相关的用例都应该归属于厨房,所以我们确认了厨房域,确认了厨房域包含的用例,这是很自然的事。

角色相关性:其次是角色,常用于划分子域,某个区域涉及多个角色参与,可以按照角色的分工,拆分为多个子域,从而满足不同角色的个性化需要。例如厨房的采购人员负责买菜、刀工负责切菜、大厨负责烹饪。我们就会考虑将厨房划分为“采购域”、“加工域”、“烹饪域”。

通常来说,子域不具备独立的问题空间,不会作为独立的领域存在。

产出物:领域、子域

以厨房域为例,如图









1.5 领域建模

这是大家比较熟知的阶段,重点分析实体与领域之间关系(领域聚合),实体与实体的关系(OO聚合)。

领域模型是实现功能的基石、需要有对功能的本质理解,才能找到最核心的实体,实体之间的OO聚合关系决定了功能的扩展性,OO聚合是最重要的核心点。







组合、聚合

聚合(aggregation):聚合关系是一种弱的关系,整体和部分可以相互独立。

组合(composition):组合关系是一种强的整体和部分的关系,整体和部分具有相同的生命周期。

可以使用如下案例,既能表达领域聚合,又能表达OO聚合的关系。





产出物:聚合、实体、值对象、实体的属性

(领域服务和事件在后续的功能设计中提供)

1.6 领域上下游

领域上下游关系,不是领域的依赖关系,依赖关系指的是能力的依赖,是共用了某些能力,依赖关系是固定的。领域上下游关系,也不是调用关系,调用关系是与用例相关的,并非描述领域处境的。

领域上下游关系指的是影响力的关系,上游影响下游,影响力分为“逻辑影响”和“数据影响”,一般说来我们更应该关注“数据影响”,所以领域上下游关系是一种数据流向的限定,是业务发生的顺序限定,用于规定该领域所使用的数据,是下游领域依赖上游领域“准备就绪”的体现。合理的上下游限定,有助于减少领域之间的不必要依赖,有利于数据的复用并减少重复计算。

领域上下游是与场景相关的,并不是一成不变的,不同的场景存在不同的上下游,各场景应该独立说明。

产出物:各场景的上下游说明



例:在【菜品管理】场景下





如果厨房的某些食材不足了,或者某个厨师休假了,就会影响到菜品的展示,从而影响到客户的订单。



例:在【客户消费】场景下





客户的订单、影响厨房生产的菜,从而影响刀工的行为,也影响到了采购。



请对比下面两个图,用于理解领域的上下游





实际上,厨师不应该依赖采购人员的采购功能,也不依赖刀工的切菜功能,他只是依赖“初加工食材”而已,而“初加工食材”就是被处理好的数据,厨师在做饭时,“初加工食材”就已经被处理好了,上面的图例只是为了说明一个关于领域上下游的问题,这是业务发生顺序以及数据来源的问题。

我们常常使用领域事件串联业务流程,在使用领域事件时,不止要关注点对点的解耦,更应该使业务流程符合领域上下游限定,让各个领域独立运行,减少领域之间的功能依赖,降低领域之间的耦合,减少业务变化带来的影响。



2、架构设计

架构设计是为了解决软件系统复杂度带来的问题,找到系统中的元素并搞清楚他们之间关系。

架构的目标是用于管理复杂性、易变性和不确定性,以确保在长期的系统演化过程中,一部分架构的变化不会对其它部分产生不必要的负面影响。这样做可以确保业务和研发效率的敏捷,让应用的易变部分能够频繁地变化,对应用的其它部分的影响尽可能地小。

架构设计三原则:合适原则、简单原则、演化原则

2.1 分层架构

我们需要按照 接口层、领域层(领域用例层、领域模型层)、依赖层、基础层 构建架构模型。

接口层:为外部提供服务的入口,是适配层的北向网关。不实现任何业务逻辑,也不处理事务,是跨领域的,是流程编排层,是门面服务。

领域用例层:是领域服务层,是领域用例的实现层、隶属于某个领域、是业务逻辑层,是事务层,业务逻辑应该在这层完整体现,不要分散到其他层级。

领域模型层:是领域模型(实体、值对象、聚合)的所在位置,专注于领域模型自身的能力,不包含业务功能,可以处理事务,是原子化的能力,是领域对象的自我实现

依赖层: 是连接外部服务的出口,是适配层的南向网关。包括仓储,端点、RPC等,主要作用是领域和外部解耦,用于保持领域的独立性,是跨领域的。

基础层:与业务无关的,与领域无关的,通用的技术能力,技术组件等。



2.2 架构映射

架构的视角,从大到小依次是:系统->应用(微服务)->模块(包)->子模块 这样的从大到小的层级。

业务领域映射:我们将划分好的领域,按照对应的视角映射为对应的元素,领域模型映射到架构模型时,应该是视角对等的,如果餐厅是系统、那么厨房就是应用,如果餐厅是应用、那么厨房就是模块。也应该层级匹配的,将用例的实现映射到用例层,将领域模型的实现映射到领域模型层。

技术和抽象问题:有时候、业务领域分析不能体现那些共性的技术问题,所以需要适当结合技术视角,可能需要对领域模型微调。同时、我们需要找到共同需要的基础能力,例如“水”、“电”、“煤气”等等,将这些作为额外的考虑因素,要做到业务问题与技术问题解耦,不要将技术问题和业务逻辑揉成一团。

领域设计,类似餐厅设计师,他设计餐厅有几个区域,区域的用途是什么。

架构设计,类似建筑设计师,他设计如何走水电煤气、如何施工等。

产出物:分层架构图



以厨房为视角,其架构如下







以餐厅为视角,其架构如下





分层架构图,体现逻辑上的层级分布,而不是代表组件的具体含义,组件是应用还是模块、需要结合实际情况而定。



2.3 必要的约束

1、分层架构越往下层就越是稳定的:下层是被上层依赖的,下层不可以反向依赖上层(扩展点除外)。因为分层架构的核心原则是将容易变化的逻辑上浮,将共性的、原子化的、通用的逻辑下沉,被依赖的下层应该是稳定的,这要求上层承接更多业务变化。下层离开上层应该是可以独立存在的,例如在接口层定义的DTO不可以在下层被使用,但领域层定义的实体可以被上层使用。

2、在使用充血模型时,应该符合面向对象编程原则:不要随意的将一些能力都充到领域实体模型中。以“菜”为例,重量和规格是“菜”的自身的属性,激发味蕾是“菜”的能力,“菜”可以维护自身的持久化状态。但是、请注意、“菜”不可以“炒菜”,因为“炒菜”的时候,“菜”还没有出现呢,“菜”不是自己的上帝,“菜”需要被做出来,所以“菜”被做出来之前是没有“菜”的,这是个时间上的概念,不要错把“炒菜”的能力放在“菜”的身上。“炒菜”用到的“水+电+气+食材+调料+厨具”不应该是“菜”的属性范围,这些元素都在“厨房”的范围中,不要让领域的模型包含不属于自身的元素,领域的实体模型只是领域的一部分,只用于实现通用的模型能力。

3、接口层和依赖层是与领域无关的:他们是与技术相关的层级,不属于任何领域,这两层不能包含业务逻辑。有时候我们可以把接口层拆为两层(接口层+应用层),也可以把依赖层拆分为两个(模型依赖、服务依赖)。

4、领域层是与环境无关的:无论某个领域是应用还是模块,都应该具备独立的用例层和独立的模型层,即使多个领域在同一个应用当中,也要按照他们是分别独立去看待,无论某个领域是应用还是模块,领域对外部的交互,不可以绕过依赖层和接口层。

5、领域应该是最小完备的:把一个领域拆分为子域、子子域、子子子...... 无限拆分,拆分到一定程度之后,某个子域就不完整了,不完整的子域是不可以独立存在的。拆分不不够或者过度拆分,都是不符合低耦合高内聚原则的。当一个领域的内部子域不具备独立性时,他们之间不必严格解耦,不需要通过依赖层访问本领域的其他子域,他们之间可以直接调用。

6、领域服务层就是领域用例层:他们俩是同一回事儿,都是用于实现领域内的用例的。不要将领域服务与领域用例视为两个独立的层,也不要将领域服务与领域模型视为同一层,否则会导致逻辑的分散(一部分在领域服务层、一部分在领域模型层、还有一部分可能在用例层),也会导致每个层的职责不明确,容易搞乱。如果将业务逻辑写在领域模型中,会导致业务逻辑进一步下沉,业务逻辑的不确定性太大,是不适合下沉的,是违反分层架构原则的。领域模型对应的是实体、领域服务对应的是用例。

7、领域用例层只能承接符合自身领域的用例:我们划分出领域的目的,就是为了区分每个领域的职责所在,因此他们必须严格按照职责办事,我们在之前已明确了用例和领域之间的关系,需要严格遵守。

8、领域模型层遵循最小依赖原则:只可以依赖必要的资源,必要资源指的是领域模型实现自身能力需要的资源,不包括实现业务逻辑包含的资源。例如领域模型需要依赖DB完成持久化,可以依赖数据访问资源,但不应该依赖其他领域资源、不可以依赖RPC资源等。



2.4 微服务划分

服务划分以领域划分为参考,主要看我们要拆分到什么粒度,这 应该符合低耦合高内聚原则,不破坏领域实体的聚合关系。

产出物:微服务



例如餐厅:是有必要拆分的,餐厅的“菜品域”,“订单域”,“厨房域”有独立的问题空间。

例如厨房:是没有必要拆分的,厨师与刀工的耦合非常高,他们都在做饭,分开之后是不完整的,分开就是没有必要的。



所以餐厅被拆分为:厨房(Kitchen)、菜品(Category)、订单(Order)三个微服务。

基于此、我们单独拿出餐厅门面服务作为接口层应用,再单独拿出餐厅基础服务作为水电煤气的应用。

一般情况下,依赖层不会作为单独的服务提供,会被以组件的形式嵌入到其他服务中。







3、功能设计(用例实现)

如果说领域设计是餐厅的设计师、架构设计是餐厅的建筑师、那么功能设计就是餐厅的厨师或服务员。

任何设计都要落地到功能设计,如果厨师不守规则,偏偏要去洗手间洗菜,最后的结果依然是一团乱,最终会导致设计无法落地。

功能设计是实现 “面向扩展开放、面向修改关闭” 的途径,是指导研发落地必备环节。



3.1 功能的概念

功能迭代时,功能会发生一些变化,所以他的含义是可能变化的,所以我们需要再次审视功能的概念,及时加以调整。

例如、我们实现了一个“做蛋炒饭”的功能,后来又实现了一个“做辣椒炒蛋”的功能,那么我们应该将功能升级为“炒菜”,甚至是“制作菜品”等。

明确功能的概念,是功能设计的前提。

产出物:更新语言库,更新脑图



3.2 用例的位置

我们在领域分析章节,已明确了用例与角色的关系,用例与领域的关系。

然而一个新功能的加入,我们仍然要再次评估,以确保他处于正确的位置。

产出物:更新用例图



3.3 事件风暴

我们需要深入功能的细节,首推的方法是事件风暴,适用于解构复杂功能。

事件风暴的作用并不限于功能分析,只是我觉得很适用于功能分析,事件风暴的一张图包含很多内容,正好是功能设计所需要的。

将功能拆分为多个子功能(步骤)。(在后续使用)

确认参与该步骤的角色和领域。(在后续的3.6章节落地)

确认步骤的串联流程和领域事件。(在后续的3.6章节落地)

确认参与该步骤的领域实体。(在后续的3.7章节落地)

产出物:事件风暴模型



3.4 用例分析

我们暂且收回思路,首先要关注共性和差异问题,以确保功能的扩展性。

确认用例的泛化+差异点,实现功能的扩展。

寻找共同包含的步骤,实现逻辑的复用。

产出物:用例分析图



例:制作菜品(做大拌菜、做铁锅炖、做炒鸡蛋、做蒸米饭、做炒米饭)







3.5 用例实现类(领域服务类)结构图

专注于用例层的类设计,实现“面相修改关闭,面相扩展开放”。

用例的类结构图是用例分析图的一种映射。

出物:用例层的类结构图







3.6 用例流程图

我们接回思路,更进一步,将事件风暴模型落实到代码层面。

我们将步骤分配到实现类中、步骤就是该类的一个方法,进一步明确由哪个类和方法来实现该步骤,从而就规定了步骤所在的领域。

我们将步骤和领域事件串联起来,规定了业务实现流程。推荐使用泳道图表达上述内容。泳道的纵向组件是用例的实现类。

这是真实业务流程的映射。

产出物:用例流程图



以炒鸡蛋为例,其用例流程图如下







3.7 活动图(时序图)

我们进一步将事件风暴模型落实到代码层面,我们使用时序图,体现依赖和调用关系,规定了步骤与领域实体模型的关系,进一步说明用例是如何实现的。

这时候,为了简便、我们可以收起领域服务类(用例层)的泳道。

产出物:时序图、活动图









试想一下、假如把业务逻辑放在领域模型当中(例如聚合),如何实现“面相扩展开放、面相修改关闭”呢?




4、编码实现

编码实现...... 我决定还是...... 偷个懒吧...... 哈哈哈。

但是我们回顾一下之前的内容,是否足够了? 不同的研发人员依照设计去编码,是否会写出不一样的代码?




系统名

餐厅系统

相关应用

厨房应用、菜品应用、订单应用,门面应用、基础应用

系统架构图



厨房应用-领域模型





厨房应用-用例层

厨师模块、刀工模块、买菜员模块

厨房应用-用例层-厨师模块(服务类结构)





厨房应用-用例层-厨师模块(类中的方法、方法含义、执行流程)





厨房应用-用例层-厨师模块(方法的依赖、调用关系)










最后、我们的目标是“解决软件复杂度带来的问题”,而实现这个目标的途径是“设计指导研发落地”。

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

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

相关文章

【docker】如何编写dockerfile文件,构建docker镜像

如何编写dockerfile文件,构建docker镜像 一、docker 镜像与 dockerfile1.1 什么是Docker镜像1.2 Docker 镜像的结构 二、dockerfile 中常用的构建指令三、dockerfile 内容示例四、构建 docker 镜像 一、docker 镜像与 dockerfile 1.1 什么是Docker镜像 Docker镜像…

虾皮跨境电商物流:为卖家提供高效灵活的物流解决方案

虾皮(Shopee)作为一家知名的跨境电商平台,其物流服务是其成功的关键因素之一。虾皮跨境电商物流服务为卖家提供了一站式的物流解决方案,从订单处理到最终交付,为卖家提供高效、灵活、成本效益高的物流服务,…

【教学类-43-13】 20240103 (4宫格数独:错误版:768套) 不重复的基础模板数量:768套

作品展示:——4宫格 768套不重复模板(64页*12套题) 有错误,实际数量小于768套 背景需求: 测试4宫格数独基础模板有几种。 写个程序,验算是不是真的是乘阶法的288种。 代码展示: 768套4宫格题…

Python for与while循环的介绍和对应练习题的巩固

for循环 重复执行同一段代码就是循环 循环列表 for val in list_name: list_num [1,2,3,4,5,6,7,8,9] for i in list_num:print(i)代码执行顺序 从上往下依次执行 遍历 通过某种顺序把某种集合所有元素都访问一遍 list_food{"火锅","烧烤","张…

流媒体学习之路(WebRTC)——GCC分析(4)

流媒体学习之路(WebRTC)——GCC分析(4) —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全配置…

服务器硬件及RAID磁盘阵列详解

文章目录 一、服务器硬件服务器常见故障 二、RAID磁盘阵列详解1、RAID磁盘阵列概述2、RAID 0(条带化存储)3、RAID 1(镜像存储)4、RAID 55、RAID 66、RAID 10(先做镜像,再做条带)7、RAID 01(先做条带&#x…

通配符证书免费获取

通配符证书是一种特殊的SSL/TLS证书,设计用于保护一个主域名及其无限数量的一级子域名。这种类型的数字证书通过使用一个星号(*)作为通配符来实现对多个子域的安全加密,使得所有与指定格式匹配的子域名都能在同一个证书下得到安全…

编程语言未来的发展方向

编程语言的未来? 编程语言发展到现在,已经是比较完善并且好用,但这仅仅是对于专业人士来说的。对于普通人来说还是遥不可及。未来编程语言会朝着更加简洁,更加易读的方向去发展,函数式编程和响应式编程会普及。编程语…

机器学习中的监督学习基本算法-逻辑回归简单介绍

逻辑回归 逻辑回归(Logistic Regression)是一种用于解决二分类问题的统计学习方法,尽管名字中带有"回归"一词,但实际上它是一种分类算法。逻辑回归的主要目标是通过学习从输入特征到一个离散的输出(通常是0…

利用Spring Cloud和Java系统设置优化工程项目管理系统源码的二次开发体验

工程项目管理涉及众多环节和角色,如何实现高效协同和信息共享是关键。本文将介绍一个采用先进技术框架的Java版工程项目管理系统,该系统支持前后端分离,功能全面,可满足不同角色的需求。从项目进度图表到施工地图,再到…

熔断、隔离、重试、降级、超时、限流,高可用架构流量治理核心策略全掌握

可用性的定义 在探讨高可用架构之前,让我们以 O2 系统为例,解释一下何谓可用性。O2 是腾讯内部的一个广告投放系统,专注于提升投放效率、分析广告效果,拥有自动化广告投放、AIGC 自动化素材生产等多种功能。 其整体架构概览如下&…

Java最新技术介绍和分析 (202305)

说明:本文完成了2023年5月份,当时最新的LTS版本是Java17,本文在撰写时参考了美团技术团队和阿里JDK团队相关的文章,以及本文也引了用文章中的图片。在此表示感谢! Java版本火车 相信老牌的Java开发者和爱好者把Java的…

【技巧】IDEA 使用小技巧(三)

IDEA 使用小技巧(三) 配置目录Ctrl 鼠标方法缩小字体 配置目录 IDEA 在使用的过程中会在 C 盘的用户目录下写入相关配置,目录如下: "C:\Users\个人用户名\AppData\Local\JetBrains" "C:\Users\个人用户名\AppDa…

Halcon顶帽运算与底帽运算的应用

Halcon顶帽运算与底帽运算的应用 文章目录 Halcon顶帽运算与底帽运算的应用1. 提取小的物件2. 校正非均匀光照 正如上文所说的,顶帽运算返回的像素部分是尺寸比结构元素小的,并且比较亮的局部小区域;底帽运算返回的像素部分是尺寸比结构元素小…

打造炫酷粒子效果的前端利器tsParticles

前端潮流速递 :打造炫酷粒子效果的前端利器tsParticles 在现代前端开发中,动画和视觉效果是吸引用户的关键元素之一。而实现炫酷而引人入胜的粒子效果,常常需要耗费大量的时间和精力。然而,有了 tsParticles,这一切变…

网络安全—PGP8.1软件应用

文章目录 安装PGP8了解工作原理 PGP使用准备工作加密与解密加密者视角(发送方)接收者视角(接收方) 签名签名方(发送方)验证签名方(接收方) 补充加密签名一段文字签名后的格式 验证解…

Java中关键词strictfp有什么作用?

在Java中,关键词strictfp用于声明一个方法、类或接口是严格遵守浮点数计算规范的。 具体作用包括: 保证浮点数计算的结果在不同平台上是一致的,避免由于浮点数计算的不精确性导致的结果不确定性。 指定了严格的浮点数计算规则,禁…

【解决】Unity 设置跨设备分辨率表现

开发平台:Unity 2018版本以上 开发语言:CSharp 编程平台:Visual Studio 2022   问题描述 使用 UnityEngine.dll 中关于设置分辨率的方法时,无法满足应用以设定分辨率进行屏幕显示问题。因而造成画面不同程度的拉伸情况。而这种情…

机器学习-基于Word2vec搜狐新闻文本分类实验

机器学习-基于Word2vec搜狐新闻文本分类实验 实验介绍 Word2vec是一群用来产生词向量的相关模型,由Google公司在2013年开放。Word2vec可以根据给定的语料库,通过优化后的训练模型快速有效地将一个词语表达成向量形式,为自然语言处理领域的应…