【DDD】学习笔记-限界上下文的控制力

引入限界上下文的目的,不在于如何划分,而在于如何控制边界。因此,我们就需要将对限界上下文的关注转移到对控制边界的理解。显然,对应于统一语言,限界上下文是语言的边界,对于领域模型,限界上下文是模型的边界,二者可以帮助我们界定问题域(Problem Space)。对于系统的架构,限界上下文确定了应用边界和技术边界,进而帮助我们确定整个系统及各个限界上下文的解决方案。可以说,限界上下文是连接问题域与解决方案域的重要桥梁

下面将分别针对业务边界、工作边界与应用边界来深入探讨限界上下文的这种控制力。

限界上下文分离了业务边界

限界上下文首先分离了业务边界,用以约束不同上下文的领域模型。这种对领域模型的划分符合架构设计的基本原则,即从更加宏观和抽象的层次去分析问题域,如此既可以避免分析者迷失在纷繁复杂的业务细节知识中,又可以保证领域概念在自己的上下文中的一致性与完整性。

例如,在电商系统中,主要的产品实体 Product 在不同的限界上下文具有不同的含义,关注的属性与行为也不尽相同。在采购上下文,需要关注产品的进价、最小起订量与供货周期;在市场上下文中,则关心产品的品质、售价,以及用于促销的精美图片和销售类型;在仓储上下文中,仓库工作人员更关心产品放在仓库的哪个位置,产品的重量与体积,是否易碎品以及订购产品的数量;在推荐上下文中,系统关注的是产品的类别、销量、收藏数、正面评价数、负面评价数。

对于这种情况,我们不应该将这一概念建模为单个类,否则就可能导致不同限界上下文对应的领域模型为了代码重用,而共享这个共同的 Product 类,导致限界上下文之间产生代码的耦合,随之而来的,与领域模型相对应的数据模型也要产生耦合,如下图所示:

enter image description here

产品(Product)实体的设计也违背了“单一职责原则(SRP)”,它包含了太多本应分离的职责,适用于不同的上下文,从而变成了一个臃肿的上帝类:

public class Product {
    private Identity id;
    private String name;
    private Category category;
    private Preriod leadTime;
    private int minimumOrderQuant;
    private Weight weight;
    private Volumn volumn;
    private int quantity;
    private long annualSales;
    private long favoritePoints;
    private long positiveComments;
    private long negetiveComments;

    public Price priceFor(CustomerType customerType) {}
    public PurchaseOrder buyFrom(Supplier supplier) {}
    public Location allocate() {}
    public boolean isFragile() {}
    public Image[] loadImagesFrom(String filePath) {}
    public Recommendations similar() {}
}

如果我们将产品看做是参与业务场景的角色,进而在不同场景中考虑对象之间的协作;那么,是否可以遵循接口隔离原则(ISP)对 Product 实体类进行抽象呢?例如,在不同的限界上下文(作为 Product 的调用者)中,确定 Product 类扮演的不同角色,然后基于面向接口设计的原则为其定义多个细粒度的接口,如 Allocation 接口、Recommendation 接口、ImageLoader 接口等。这样的接口即 Martin Fowler 提出的角色接口(Role Interface),然后,再让定义的 Product 类去实现这多个接口,体现了“大对象小角色”的设计思路。

如果只考虑设计层面,这样基于接口隔离原则进行设计的方案是合理的。例如,我们可以在各自的限界上下文中定义这些接口,然而,实现了这些接口的 Product 类又应该放在哪里?譬如说,我们可以引入一个产品上下文,然后在其内部定义 Product 类去实现这些接口。这样的设计是不合理的,它导致了产品上下文同时依赖其余四个限界上下文,形成了架构层面上限界上下文之间不必要的耦合,如下所示:

enter image description here

引入的限界上下文对设计产生了影响。在考虑设计方案时,我们需要时刻警醒限界上下文边界的控制力。限界上下文内部的协作成本要远远低于限界上下文之间的协作成本。在面向对象设计中,行之有效的“接口隔离原则”如果跨越了多个限界上下文,就变得不合理了。为了避免重复,我们引入了耦合,这种设计上的顾此失彼是不可取的。要降低耦合同时又能避免重复,更好的解决方案是让每一个限界上下文拥有自己的领域模型,该领域模型仅仅满足符合当前上下文需要的产品唯一表示。这其实是领域驱动设计引入限界上下文的主要目的:

enter image description here

虽然不同的限界上下文都存在相同的 Product 领域模型,但由于有了限界上下文作为边界,使得我们在理解领域模型时,是基于当前所在的上下文作为概念语境的。这样的设计既保证了限界上下文之间的松散耦合,又能够维持限界上下文各自领域模型的一致性,此时的限界上下文成为了保障领域模型不受污染的边界屏障。

限界上下文明确了工作边界

一个理想的开发团队规模最好能符合亚马逊公司提出的“Two-Pizza Teams”,即 2PTs 规则,该规则认为“让团队保持在两个披萨能让成员吃饱的小规模”,大体而言,就是将团队成员人数控制在 7~10 人左右。为何要保证这样的规模呢?因为小团队能够更有效保证有效的沟通,如下图所示:

enter image description here

2PTs 规则自有其科学依据。如果我们将人与人之间的沟通视为一个“联结(link)”,则联结的数量遵守如下公式,其中 n 为团队的人数:

[Math Processing Error]N(link)=n(n−1)2

联结的数量直接决定了沟通的成本,以 6 人团队来计算,联结的数量为 15。如果在原有六人团队的规模上翻倍,则联结数陡增至 66。对于传统项目管理而言,一个 50 人的团队其实是一个小型团队,根据该公式计算得出的联结数竟然达到了惊人的 1225。如下图所示,我们可以看到随着团队规模的扩大,联结数的增长以远超线性增长的速度发展,因而沟通的成本也将随之发生颠覆性的改变:

enter image description here

随着沟通成本的增加,团队的适应性也会下降。Jim Highsmith 在 Adaptive Software Development 一书中写道:

最佳的单节点(你可以想象成是通信网络中可以唯一定位的人或群体)联结数是一个比较小的值,它不太容易受网络规模的影响。即使网络变大,节点数量增加,每个节点所拥有的联结数量也一定保持着相对稳定的状态。

要做到人数增加不影响到联结数,就是要找到这个节点网络中的最佳沟通数量,也即前面提到的 2PTs 原则。然而团队规模并非解决问题的唯一办法,如果在划分团队权责时出现问题,则团队成员的数量不过是一种组织行为的表象罢了。如果结合领域驱动设计的需求,则我们应该考虑在保持团队规模足够小的前提下,按照软件的特性(Feature)而非组件(Component)来组织软件开发团队,这就是所谓“特性团队”与“组件团队”之分。

传统的“组件团队”强调的是专业技能与功能重用,例如,熟练掌握数据库开发技能的成员组建一个数据库团队,深谙前端框架的成员组建一个前端开发团队。这种遵循“专业的事情交给专业的人去做”原则的团队组建模式,可以更好地发挥每个人的技能特长,然而牺牲的却是团队成员业务知识的缺失,客户价值的漠视。这种团队组建模式也加大了团队之间的沟通成本,导致系统的整体功能无法持续和频繁的集成。例如,由于业务变更需要针对该业务特性修改用户描述的一个字段,就需要从数据存储开始考虑到业务模块、服务功能,最后到前端设计。一个小小的修改就需要横跨多个组件团队,这种交流的浪费是多么不必要啊。在交流过程中,倘若还出现了知识流失,或者沟通不到位导致修改没有实现同步,就会带来潜在的缺陷。这种缺陷非常难以发现,即使在高覆盖率的集成测试下暴露了,缺陷定位、问题修复又是一大堆破事儿,需要协调多个团队。邮件沟通、电话沟通、你来我往、扯皮推诿,几天的时光如白驹过隙、转眼就过,问题还未必得到最终的解决。倘若这样的组件团队还是不同供应商的外包团队,分处于不同城市,可以想象这样的场景是多么“美好”!很“幸运”,我在参与某汽车制造商的零售商管理系统时,作为 CRM 模块的负责人,就摊上了这样的破事儿,如今思之,仍然不寒而栗啊!

为了规避这些问题,组建特性团队更有必要。所谓“特性团队”,就是一个端对端的开发垂直细分领域的跨职能团队,它将需求分析、架构设计、开发测试等多个角色糅合在一起,专注于领域逻辑,实现该领域特性的完整的端对端开发。一个典型的由多个特性团队组成的大型开发团队如下图所示:

enter image description here

如上图所示,我们按照领域特性来组建团队,使得团队成员之间的沟通更加顺畅,至少针对一个领域而言,知识在整个特性团队都是共享的。当然,我们在上图中也看到了组件团队的存在。这是因为在许多复杂软件系统中,毕竟存在一些具有相当门槛的专有功能,需要具有有专门知识或能够应对技术复杂度的团队成员去解决那些公共型的基础型的问题。二者的结合可以取长补短,但应以组建特性团队为主。

特性团队专注的领域特性,是与领域驱动设计中限界上下文对应的领域是相对应的。当我们确定了限界上下文时,其实也就等同于确定了特性团队的工作边界,确定了限界上下文之间的关系,也就意味着确定了特性团队之间的合作模式;反之亦然。之所以如此,则是因为康威定律(Conway’s Law)为我们提供了理论支持。

康威定律认为:“任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。” 在康威定律中起到关键杠杆作用的是沟通成本。如果同一个限界上下文的工作交给了两个不同的团队分工完成,为了合力解决问题,就必然需要这两个团队进行密切的沟通。然而,团队间的沟通成本显然要高于团队内的沟通成本,为了降低日趋增高的成本,就需要重新划分团队。反过来,如果让同一个团队分头做两个限界上下文的工作,则会因为工作的弱相关性带来自然而然的团队隔离。

enter image description here

如上图所示,我们可以设想这样一种场景,如果有两个限界上下文的工作,分配给两个不同的团队。分配工作时,却没有按照限界上下文的边界去组建团队,即每个团队会同时承担两个限界上下文的工作。试想,这会造成多少不必要的沟通成本浪费?借用 ORM(Object Relational Mapping,对象关系映射)的概念,将这种职责分配的错位称之为“限界上下文与团队的阻抗不匹配”。如果能够将团队与限界上下文重合,就能够降低沟通成本,打造高效的领域特性团队,专注于属于自己的限界上下文开发。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

muduo网络库剖析——事件循环线程池EventLoopThreadPool类

muduo网络库剖析——线程Thread类 前情从muduo到my_muduo 概要框架与细节成员函数使用方法 源码结尾 前情 从muduo到my_muduo 作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精…

Spring结合工厂模式

学习设计模式,不要进入一个误区生搬硬套,它是一种编程思想,结合实际使用,往往设计模式是混合使用的 工厂模式 核心本质:使用工厂统一管理对象的创建,将调用者跟实现类解耦 我这里使用Spring容器的支持&am…

latent-diffusion model环境配置--我转载的

latent-diffusion model环境配置,这可能是你能够找到的最细的博客了_latent diffusion model 训练 autoencoder-CSDN博客 前言 最近在研究diffusion模型,并对目前最火的stable-diffusion模型很感兴趣,又因为stable-diffusion是一种latent-di…

【QT+QGIS跨平台编译】之十五:【libTiff+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、libTiff介绍二、文件下载三、文件分析四、pro文件五、编译实践一、libTiff介绍 libTiff是一个用于处理TIFF图像文件格式的开源软件库。 TIFF(Tagged Image File Format)是一种灵活且广泛支持的图像文件格式,常用于存储照片和其他高质量图像。libTiff提供了一套…

Qt QPlainTextEdit高亮显示当前行

Qt QPlainTextEdit高亮显示当前行 文章目录 Qt QPlainTextEdit高亮显示当前行摘要错误的代码正确的代码QTextEdit::ExtraSelection 关键字: Qt、 QPlainTextEdit、 QTextBlock、 ExtraSelection、 GPT 摘要 今天要在说一下GPT,当下如果你还不会用G…

STM32读取MPU6050数据并通过角度值控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机)

通过STM32F103C8T6读取MPU6050数据控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机) 最终现象一、MPU6050数据读取二、舵机控制原理①什么是PWM?②STM32F103C8T6如何生成PWM?③控制舵机需要什么样的PWM波? 三…

qemu调试kernel启动(从第一行汇编开始)

一、背景 大部分qemu调试kernel 都是讲解从start_kernel开始设置断点,然后开启调试; 但是我们熟悉linux启动流程的伙伴肯定知道,在start_kernel之前还有一段汇编,包括初始化页表及mmu等操作, 这部分如何调试呢&#x…

cocos添加节点事件的3种方式

我们以button为例来说明一下cocos怎样为节点添加事件: 直接通过cocos熟悉检查器绑定 添加事件脚本 import { _decorator, Component, Node, input, Input, Button, EventKeyboard } from cc; const { ccclass, property } _decorator;ccclass(Attack) export cla…

【vue】图片加载骨架

一、前言 在网速较低或者网站的服务器宽带只有几MB的情况下,网页中的图片加载时,要么空白,要么像打印机一样一行一行地“扫描”出来,为了提升用户体验,可以给图片标签外加一层骨架。 无骨架 有骨架 二、详细设计 每张…

无人机在三维空间中的转动问题

前提 这篇博客是对最近一个有关无人机拍摄图像项目中所学到的新知识的一个总结,比较杂乱,没有固定的写作顺序。 无人机坐标系旋转问题 上图是无人机坐标系,绕x轴是翻滚(Roll),绕y轴是俯仰(Pitch),绕z轴是偏航(Yaw)。…

sqli-labs第一关

1.判断是否存在注入,注入是字符型还是数字型? ?id1 and 11 ?id1 and 12 因为输入and 11与and 12 回显正常,所以该地方不是数字型。 ?id1 ?id1-- 输入单引号后报错,在单引号后添加--恢复正常,说明存在字符注入 2.猜解SQL查…

Spark Exchange节点和Partitioning

​Exchange 在explain时,常看到Exchange节点,这个节点其实就是发生了数据交换 此图片来自于网络截取 BroadcastExchangeExec 主要是用来广播的 ShuffleExchangeExec 里面决定了数据分布的方式和采用哪种shuffle 在这里可以看到好几种不同的分区器 shuf…

Windows11搭建GPU版本PyTorch环境详细过程

Anaconda安装 https://www.anaconda.com/ Anaconda: 中文大蟒蛇,是一个开源的Python发行版本,其包含了conda、Python等180多个科学包及其依赖项。从官网下载Setup:点击安装,之后勾选上可以方便在普通命令行cmd和PowerShell中使用…

聊聊Git合并和变基

一、 Git Merge 合并策略 1.1 Fast-Forward Merge&#xff08;快进式合并&#xff09; //在分支1下操作&#xff0c;会将分支1合并到分支2中 git merge <分支2>最简单的合并算法&#xff0c;它是在一条不分叉的两个分支之间进行合并。快进式合并是默认的合并行为&#…

微信小程序wx.getRealtimeLogManager无法查看log内容

解决方案&#xff1a; 首先&#xff0c;检查在we分析是否启用实时日志&#xff0c;入口如下&#xff1a; 其次&#xff0c;检查基本语法是否正确&#xff0c;参考如下&#xff1a; var logger wx.getRealtimeLogManager() logger.error("error message") 最后&a…

你好,C++对象

你好&#xff0c;对象 面向对象开发对象的定义 类与对象类的定义类的访问限定符及封装类的实例化类对象模型结构体内存对齐规则 this指针this指针的引入 this指针的特性 类的默认成员函数构造函数析构函数拷贝构造函数结语 面向对象开发 对象的定义 对象的含义是指具体的某一…

在docker中安装MQTT教程

网上的好多关于在docker中安装MQTT教程都是错误的不完整的。这篇博客是完整的&#xff0c;实践过的&#xff0c;踩过了很多的坑得来的&#xff0c;欢迎大家享用&#xff01; 1、首先在docker中拉取镜像 docker pull eclipse-mosquitto2、创建配置文件目录 mkdir -p /docker/…

axure如何载入元件库

首先&#xff0c;安装axure后打开 1.打开axure软件后&#xff0c;左侧可以看到一个叫元件库的菜单 2.点击元件库右侧的选项按钮 然后在出现的下拉菜单中点击载入元件库 3.找到自己下载好的元件库&#xff08;Axure元件库合集&#xff09;&#xff0c;双击它就可以载入了 可以看…

Linux:共享内存

文章目录 System V共享内存的原理管理共享内存shmgetshmatshmdtshmctl 共享内存和管道实现进程间同步通信 前面介绍完了匿名管道和命名管道&#xff0c;那么本篇要引入的主题是共享内存 System V 作为进程通信部分的内容&#xff0c;共享内存必然有其存在的意义和价值&#x…

༺༽༾ཊ—Unity之-04-工厂方法模式—ཏ༿༼༻

首先创建一个项目&#xff0c; 在这个初始界面我们需要做一些准备工作&#xff0c; 建基础通用文件夹&#xff0c; 创建一个Plane 重置后 缩放100倍 加一个颜色&#xff0c; 任务&#xff1a;使用工厂方法模式 创建 飞船模型&#xff0c; 首先资源商店下载飞船模型&#xff0c…