DDD单根 聚合根 实体 值对象

news2024/9/24 13:24:11

前言

2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD。快二十年的时间,领域驱动设计在不断地发展,后微服务时代强调的东西,在国外大家都热衷于领域驱动设计解决业务复杂度,在国内吧,我发现除了大厂以外,你和他说,完全不明白,可能很多人对于什么是面向对象开发,都不明白,什么才是真正的面向对象开发;也是在学习中成长着,我建议是从 《设计模式-可复用面向对象软件的基础》 《领域驱动设计:软件核心复杂性应对之道》《实现领域驱动设计》《解构领域驱动设计》 等等这些书看着走,多在项目中实践,就会明白它想给我们创建一个怎样的软件,如何用领域驱动设计应对当今这些复杂的业务逻辑。

首先都是思想(不是技术),我们要明白;所以它很抽象,很难总结出一套方法解构论,就是很抽象,抽象的东西理解起来就很困难,这就是为什么国内一直 想用,但非常难;其实到 解构领域驱动设计 这本书 21 年出版的,我渐渐地发现吧,已经在往“八股文”方向套了;要不然你看前两本书 全是概念,除了大神还可以摸索出来,但是一般人使用了反而更拉。

可以推荐大家 一个学习ddd的网站的,国内的。

https://www.jdon.com/ddd.html

什么是DDD

领域驱动设计(Domain-Driven Design,简称DDD)

业务初期,我们的功能大都非常简单,普通的CRUD就能满足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂。模块彼此关联,谁都很难说清模块的具体功能意图是啥。修改一个功能时,往往光回溯该功能需要的修改点就需要很长时间,更别提修改带来的不可预知的影响面。

原型 实体 值对象 ,构成聚合根,都在一个领域里面的。 主要是将领域划边界,方法和领域共存, 然后控制出 领域里面的行为 (方法) 其实将业务服务打的更散,放到对象中, 后面的变化不断应对,后期维护是相当快的。 贫血模型 和充血模型, 这个概念后面说一下。

在刚开始开发的时候,我们是这么设计的,随着我们的业务不断发展,我们的项目不断扩大,同时我们的表也是一个订单大表,包含了非常多字段。在我们维护代码时,牵一发而动全身,很可能只是想改下商品的功能,却影响到了创单核心路径。虽然我们可以通过测试保证功能完备性,但当我们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各种问题。

上述问题,归根到底在于系统架构不清晰,划分出来的模块内聚度低、高耦合。

订单商品模块,假设我们随着数据库区设计:

首先建立模型:

class goods{
   String id;//主键
   String skuId;//唯一识别号
   String goodsName;
   Bigdecimal price;
   Category category;//分类
   List<Specification> specifications;//规格 
   ... 
}

class Order{
   String id;//主键
   String orderNo;//订单号
   List<OrderItem> orderItems;//订单明细
   BigDecimal orderAmount;//总金额
   ...
}

class OrderItem{
   String id;
   Goods goods;//关联商品
   BigDecimal snapshotPrice;//下单时的价格
}

考虑到了订单要保存下单时候的价格(当然,这是常识)但这么设计却存在诸多的问题。在分布式系统中,商品和订单这两个模块必然不在同一个模块,也就意味着不在同一个网段中。上述的类设计中直接将Product的列表存储到了Order中,也就是一对多的外键关联。这会导致,每次访问订单的商品列表,都需要发起n次远程调用。

反思设计,其实我们发现,订单BC的Product和商品BC的Product其实并不是同一个entity,在商品模块中,我们更关注商品的规格,种类,实时价格,这最直接地反映了我们想要买什么的欲望。而当生成订单后,我们只关心这个商品买的时候价格是多少,不会关心这个商品之后的价格变动,还有他的名称,仅仅是方便我们在订单的商品列表中定位这个商品。

重点是,领域设计思路需要去脱离数据库的桎梏,最高的预期是根据界限去完成数据库设计,最次。。不需要数据库来绑架我们的系统设计。业务才是王道,一个架构师的核心价值不仅仅体现在框架的应用上,最关键在于能够将我们的系统设计安排得明明白白。

如何改造

class OrderItem{
    String id;
    String productId;//只记录一个id用于必要的时候发起command操作
    String skuId;
    String productName;
    ...
    BigDecimal snapshotPrice;//下单时的价格
}

做了一定的冗余,这使得即使商品模块的商品,名称发生了微调,也不会被订单模块知晓。这么做也有它的业务含义,用户会声称:我买的时候他的确就叫这个名字。记录productId和skuId的用意不是为了查询操作,而是方便申请售后一类的命令操作(command)。

在这个例子中,Order 和 goods都是entity,而OrderItem则是value object(想想之前的定义,OrderItem作为一个类,的确是描述了Order这个entity的一个属性集合)。关于标识,我的理解是有两层含义,第一个是作为数据本身存储于数据库,主键id是一个标识,第二是作为领域对象本身,orderNo是一个标识,对于人而言,身份证是一个标识。而OrderItem中的productId,id不能称之为标识,因为整个OrderItem对象是依托于Order存在的,Order不存在,则OrderItem没有意义。

单根 聚合根

在《解构领域驱动设计》 这本书中 是将聚合作为边界的象征,作为所有领域的入口。

聚合(aggregate)是一种边界’它可以封装_到多个实体与值对象’并维持该 边界范围之内的业务完整性°聚合至少包含_个实体’且只有实体才能作为聚合根(aggregateroot)。 工厂(鱼ctory)和资源库(repository)(参见第17章)负责管理聚合的生命周期。前者负责聚合的 创建’用于封装复杂或者可能变化的创建逻辑;后者负责从存放资源的位置(数据库、内存或者其 他Web资源)获取、添加、删除或者修改聚合。

要访问聚合只能通过聚合根的资源库,这就隐式地划定了边界和 入口,有效控制了聚合内所有类型的领域对象。若聚合的创建逻辑较为复杂或存在可变性’可引入工 厂来创建聚合内的领域对象·

聚台的定义与特征

Eric Evans阐释了何谓聚合(aggregate)模式:“将实体和值对象划分为聚合并围绕着聚合定义 边界。选择-个实体作为每个聚合的根’并允许外部对象仅能持有聚合根的引用。作为_个整体来 定义聚合的属性和不变量’并将执行职责赋予聚合根或指定的框架机制°”这一定义说明了聚合的 基本特征。

聚合是包含了实体和值对象的—个边界。 聚合内包含的实体和值对象形成-棵树’只有实体才能作为这棵树的根°这个根称为聚合 根(aggegateroot)’这个实体称为根实体(rootentity)° □外部对象只允许持有聚合根的引用, 以起到边界的控制作用。 □聚合作为_个完整的领域概念整体’其内部会维护这个领域概念的完整性,体现业务上 不变量约束° □由聚合根统_对外提供履行该领域概念职责的行为方法,实现内部各个对象之间的行为 协作。

类似于

四种领域模型

  • 失血模型

  • 贫血模型

  • 充血模型

  • 胀血模型

修改商品为例来举例模型的概念

class goods{
    String id;
    String skuId;//唯一识别号
    String goodsName;
}

失血模型**:略过,可以理解为所有的操作都是直接操作数据库。

贫血模型

class GoodsDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void updateName(String name,String id){
        jdbcTemplate.excute("update goods u set u.goods_name = ? where id=?",name,id);
    }
}

class UserService{

    @Autowired
    UserDao userDao;

    void updateName(String name,String id){
        userDao.updateName(goodsName,id);
    } 
}

贫血模型中,dao是一类sql的集合,在项目中的表现就是写了一堆sql脚本,与之对应的service层,则是作为Transaction Script的入口。观察仔细的话,会发现整个过程中user对象都没出现过。

充血模型

interface UserRepository extends JpaRepository<Goods,String>{
    //springdata-jpa自动扩展出save findOne findAll方法
}

class UserService{
    @Autowoird
    UserRepository userRepository;

    void updateName(String name,String id){
        Goods goods = goodsRepository.findOne(id);
        goods.setName(name);
        goodsRepository.save(user);
    }
}

充血模型中,整个修改操作是“隐性”的,对内存中goods对象的修改直接影响到了数据库最终的结果,不需要关心数据库操作,只需要关注领域对象goods本身。Repository模式就是在于此,屏蔽了数据库的实现。与贫血模型中goods对象恰恰相反,整个流程没有出现sql语句。

涨血模型:

没有具体的实现,可以这么理解:

void updateName(String name,String id){
    Goods goods = new Goods(id);
    goods.setName(name);
    goods.save();
}

我们在Repository模式中重点关注充血模型。

实体 值对象

在领域驱动模型中,战术模型:

实体

实体(entity)这个词被我们广泛使用’甚至过分使用。设计数据库时,我们用到实体, Len Silverston就说:“实体是一个重要的概念,企业希望建立和存储的信息都是关于实体的信息。’’在分解系统的组成部分时’我们用到实体’EdwardCrawley等人就说:“实体也称为部件、模块、 例程、配件等’就是用来构成全体的各个小块°”

一个典型的实体应该具备3个要素:

  • 身份标识;

  • 属性;

  • 领域行为°

根据ID的共同特征’可以定义一个通用的接口:

通用类型和领域类型ID的区别仅在于值是否代表丁业务含义。作为实体的身份标识,它们都 具有业务价值

实体的属性用来说明主体的静态特征,并持有数据与状态。通常,我们会依据粒度的粗细将 属性分为原子属性与组合属性。定义为开发语言内建类型的属性就是原子属性’如整型、布尔型、 字符串类型等,表述了不可再分的属性概念。

领域行为

实体拥有领域行为,可以更好地说明其作为主体的动态特征。

值对象

值对象(valueohject)通常作为实体的属性,也就是亚里士多德提到的分量、性质、关系、场 所、时间、位置姿态等范畴。正如Eirc Evans所说,“当我们只关心一个模型元素的属性时,应把 它归类为值对象°我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。值对 象应该是不可变的。不要为它分配任何标识′而且不要把它设计成像实体那么复杂 。“

值对象与实体的本质区别

一个领域概念到底该用值对象还是实体类型,第一个判断依据是看业务的参与者对它的相等

判断是依据值还是依据身份标识°—前者是值对象’后者是实体。

值对象具有的特性:

  • 对象创建以后其状态就不能修改;

  • 对象的所有字段都是final类型;

  • 对象是正确创建的(创建期间没有this引用溢出)。

领域行为 : 值对象的名称容易让人误会它只该拥有值’不应拥有领域行为。

实际上,只要采用了对象建 模范式,无论实体对象还是值对象,都需要遵循面向对象设计的基本原则,如信息专家模式,将操 作自身数据的行为分配给它。EircEvans之所以将其命名为值对象,是为了强调对它的领域概念身 份的确认,即关注重点在于值。

微服务架构中的DDD应用

在微服务架构中,我们提倡的是低耦合,高内聚,那么需要达到低耦合高内聚这个目标,我们需要去如何应用DDD领域的概念去完成呢?

在DDD领域中,提供了我们一个非常有意思的东西,叫做界限上下文.界限上下文是怎么来的,我们肯定需要知道,我们要理解一个领域的概念。

以服务端而言,我们需要来界定领域,这时候我们需要来对需求文档进行分析:

  1. 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;

  1. 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;

  1. 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;

  1. 为聚合根设计仓储,并思考实体或值对象的创建方式;

  1. 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

领域

现实世界中,领域包含了问题域和解系统。一般认为软件是对现实世界的部分模拟。在DDD中,解系统可以映射为一个个限界上下文,限界上下文就是软件对于问题域的一个特定的、有限的解决方案。

那么我们的服务端假设只有两个领域--一个是订单,一个是商品,那么我们可以把商品领域进行进一步的细分:

假设商品需求如下(事实上这个和用户角色进行了挂钩,我们先不用去太在意这个,先来理解下领域):

买方商品--可见购买商品,购买者可以看到所有商品(进行价格排序)。

卖方商品--可以去上架商品,上架成功之后购买者就能够看到商品。

供应商商品--可以给销售者提供商品,销售者的商品需要在销售列表中可被选择。

在每一个边界就形成了界限上下文。

在进行上下文划分之后,我们还需要进一步梳理上下文之间的关系。

康威(梅尔·康威)定律任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

康威定律告诉我们,系统结构应尽量的与组织结构保持一致。这里,我们认为团队结构(无论是内部组织还是团队间组织)就是组织结构,限界上下文就是系统的业务结构。因此,团队结构应该和限界上下文保持一致。

拓展:墨菲定律--每当你觉得可能会发生的时候,这件事一定会发生。

通过我们界限上下文的划分,我们可以开始对商品服务内部进行处理:

import com.dn.goods.bussiness.buyer. ;//买方上下文
import com.dn.goods.bussiness.seller.;//卖方上下文
import com.dn.goods.bussiness.supplier.*;//供应商上下文

整个DDD领域驱动设计,国内还不够成熟,对于小型的项目,我觉得可以使用来作为实验,因为很多时候,你在说撒,可能别个都不懂,这就很尴尬了,更不用说是开发东西了。

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

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

相关文章

Nginx网站服务及优化

Nginx网站服务及优化一、简介1、Nginx概述2、Nginx和Apache的优缺点比较3、Nginx和Apache最核心的区别二、Linux中的I/O三、Nginx编译安装详细1、关闭防火墙、安装依赖关系2、新建用户nginx便于管理3、将压缩包传入到/opt目录下&#xff0c;编译安装4、做软连接并启动nginx5、创…

软件测试简历个人技能和项目经验怎么写?(附项目资料)

目录 前言 个人技能 项目实战经验 项目名称&#xff1a;苏州银行项目&#xff08;webapp&#xff09; 项目描述&#xff1a; 项目名称&#xff1a;中国平安项目&#xff08;webapp&#xff09; 项目描述&#xff1a; 项目名称&#xff1a;苏宁易购项目&#xff08;webapp&a…

软件体系结构(期末复习)

文章目录软件体系结构软件体系结构概论软件体系结构建模软件体系结构风格统一建模语言基于体系结构的软件开发软件体系结构 软件体系结构概论 软件危机是指计算机软件的开发和维护过程中遇到的一系列严重问题。 软件危机的表现: 软件危机的原因: 软件工程的基本要素&#xf…

轻松上手nacos使用

三步上手nacos使用1.为什么使用nacos?2.如何使用nacos1.为什么使用nacos? 1.服务发现中心。 微服务将自身注册至 Nacos&#xff0c;网关从 Nacos 获取微服务列表。 2.配置中心。 微服务众多&#xff0c;它们的配置信息也非常复杂&#xff0c;为了提高系统的可维护性&#xf…

每天一个linux命令---awk

awk命令 1. 简介 awk是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具&#xff0c;grep、sed、awk并称为shell中文本处理的三剑客。 AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&am…

1.OCR--文本检测算法FCENet

文章目录1.简介2.主要工作2.1 傅里叶轮廓嵌入(Fourier Contour Embedding)2.2 FCE模型3.代码实现参考资料欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 论文:Fourier Contour Embedding for Arbitrary-Shaped Text Detection 1.简介 这…

设计模式之中介模式与解释器模式详解和应用

目录1 中介模式详解1.1 中介模式的定义1.1.1 中介者模式在生活场景中应用1.1.2 中介者模式的使用场景1.2 中介模式的通用实现1.2.1 类图设计1.2.2 代码实现1.3 中介模式应用案例之聊天室1.3.1 类图设计1.3.2 代码实现1.4 中介者模式在源码中应用1.4.1 jdk中Timer类1.5 中介者模…

logd守护进程

logd守护进程1、adb logcat命令2、logd守护进程启动2.1 logd文件目录2.2 main方法启动3、LogBuffer缓存大小3.1 缓存大小优先级设置3.2 缓存大小相关代码位置android12-release1、adb logcat命令 命令功能adb bugreport > bugreport.txtbugreport 日志adb shell dmesg >…

kafka-11-kafka的监控工具和常用配置参数

kafka官方文档 参考Kafka三款监控工具比较 1 查看kafka的版本 进入kafka所在目录&#xff0c;通过查看libs目录下的jar包。 2.11是scala的版本&#xff0c;2.0.0是kafka的版本。 测试环境 #systemctl start zookeeper #systemctl start kafkka 2 kafka的常用配置 Kafka使用…

SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】

SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】 目录SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】一、创建一个SpringBoot项目二、修改pom.xml中SpringBoot的版本三、配置文件3.1 application-dev.ym…

2.TCP/UDP什么时候选择,HTTP,使用TCP/UDP的协议有哪些,TCP三次握手四次挥手大概流程,为什么要三次握手.

文章目录1.什么时候选择 TCP,什么时候选 UDP?2. HTTP 基于 TCP 还是 UDP&#xff1f;3.使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?4.TCP 三次握手和四次挥手&#xff08;非常重要、传输层&#xff09;5.为什么要三次握手?1.什么时候选择 TCP,什么时候选 UDP? UDP 一般…

Spring Cloud Nacos实战(五)- 命名空间分组和DataID三者关系

Nacos命名空间分组和DataID三者关系 名词解释 命名空间&#xff08;Namespace&#xff09; ​ 用于进行租户粒度的配置隔离。不同的命名空间下&#xff0c;可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离&#xff0c;例如开发…

Kubernetes一 Kubernetes之入门

二 Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应…

机器学习实战教程(六):决策树

决策树 决策树是什么&#xff1f;决策树(decision tree)是一种基本的分类与回归方法。举个通俗易懂的例子&#xff0c;如下图所示的流程图就是一个决策树&#xff0c;长方形代表判断模块(decision block)&#xff0c;椭圆形成代表终止模块(terminating block)&#xff0c;表示…

SpringBoot实战——个人博客项目

目录 一、项目简介 二、项目整体架构 数据库模块 后端模块 前端模块 三、项目具体展示 四、项目的具体实现 1、一些准备工作 &#x1f34e;数据库、数据表的创建 &#x1f34e;设置数据库和MyBatis的配置 &#x1f34e;将前端项目引入到当前项目中 2、登录注册模块 &…

CV学习笔记-Inception

CV学习笔记-Inception 目录 文章目录CV学习笔记-Inception目录1. 常见的卷积神经网络2. Inception(1) Inception提出背景(2) Inception module 核心思想3. Inception的历史版本(1) InceptionV1-GoogleNet(2) InceptionV2(3) InceptionV3(4) Inception V44. Inception模型的特点…

一起学 pixijs(4):如何绘制文字md

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天我们来学 pixijs 如何绘制文字。pixijs 版本为 7.1.2。 使用原生的 WebGL 来绘制文字是非常繁琐的&#xff0c;pixijs 对此进行了高层级的封装&#xff0c;提供了 Text 类和 BitMapText 类来绘制文字。 Text 最基本的写法&…

【Flutter入门到进阶】Dart进阶篇---Dart异步编程

1 并行与并发的编程区别 1.1 并发与并行 1.1.1 说明 我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车辆就是 8 辆此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可以并行运行。 CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数…

Ubuntu20.04如何安装虚拟机(并安装Android)

安装虚拟机&#xff08;KVM&#xff09;这种KVM只能安装windows无法安装安卓(From https://phoenixnap.com/kb/ubuntu-install-kvm)A type 2 hypervisor enables users to run isolated instances of other operating systems inside a host system. As a Linux based OS, Ubun…

Redis第四讲

目录 四、Redis04 4.1 Redis集群应用场景 4.2 集群 4.2.1 基本原理 4.2.2 主从复制的作用 4.3 配置集群&#xff08;一台虚拟机&#xff09; 4.3.1 规划网络 4.3.2 创建节点 4.3.3 创建目录 4.3.4 配置redis7001.conf 4.3.5 配置其余文件 4.3.6 后台启动redis 4.3…