DDD领域驱动设计学习

DDD领域驱动设计

  • 早期设计流程图
    • image-20230305134926026
  • 面向对象的系统设计流程
    • image-20230305135052710

DDD落地到数据库

  • 领域对象持久化思想
    • 将暂时不使用的对象从内存中持久化存储到磁盘中,再次使用这个领域对象时,根据key值到数据库中查找这条记录,然后将其恢复成领域对象,应用程序就可以继续使用它
  • DDD的数据库设计变成了以领域模型为核心,如何将领域模型转换成数据库设计的过程

传统的4中关系直接转换

  • image-20230305135949429
  • image-20230305140004325
  • image-20230305140116258
  • image-20230305140232659
  • image-20230305140409255

继承关系的3种设计方案

  • image-20230305140517862
    • 优点:简单,整个继承关系的数据全部保存在这个表
    • 缺点:造成”表稀疏“
  • image-20230305140632892
  • image-20230305140723993
  • image-20230305140745391

转换成NoSQL数据

  • 关系型数据库,遵循第三范式,使得数据库能够大幅度降低冗余,但是数据库查询需要频繁使用join操作,在高并发场景性能低下
  • NoSQL数据库,将join的查询在写入数据库表前先进行join操作,直接写到一张表单中进行分布式存储,这张表称为“宽表”
  • image-20230305141350671
  • image-20230305141359864

领域模型指导程序设计

服务(Service)

  • 标识的是在领域对象之外的操作与行为,接收用户的请求和执行某些操作
  • 当用户在系统界面中进行操作时,会向系统发送请求,“服务”去接收用户的这些请求,然后根据需求去执行相应的方法,所有操作都完成后,再将实体或者值对象中的数据持久化到数据库

实体(Entity)

  • 通过一个唯一标识字段来区分真实世界中的每一个个体的领域对象
  • 可变性是实体的特点

值对象

  • 代表的是真实世界中那些一成不变的、本质性的事物
  • 不变性是值对象的本质

贫血模型与充血模型

  • 贫血模型

    • 有很多POJO对象,除了一堆get/set方法,几乎没有任何业务逻辑
    • image-20230305144154949
    • image-20230305144218202
    • 相比充血模型,更加简单易行
  • 充血模型

    • 将领域模型中的原貌直接转换为程序中的领域设计,各种服务操作不在服务中实现,而是在领域对象中实现
    • image-20230305144315005
    • 保持了领域模型的原貌,代码修改起来比较直接
    • 保持了对象的封装,已于变更
  • 贫血模型比充血模型更加简单易行

    • image-20230305144948078
    • image-20230305144843582
  • 充血模型需要具备更强的设计与协作能力

    • 充血模型需要开发人员具有更强的OOA/D能力,分析业务、业务建模与设计能力
    • 充血模型需要有较强的团队协作能力
    • 贫血模型所有业务处理过程都交给Service去完成
  • 贫血模型更容易应对复杂的业务场景

    • 采用贫血模型,将复杂的业务处理场景,划分成多个独立的步骤然后将这些独立的步骤分配给多个Service串联起来执行
    • image-20230305150455673
  • 将需要的业务逻辑放到领域对象中,按照充血模型去设计

    • 在领域模型中出现类似继承、多态的情况
    • 软件设计的过程中将一些类型或者编码进行转换
    • 在软件设计中更好的表现领域对象之间的关系
    • “聚合”,在真实世界中代表整体与部分的事务
  • 除此之外的其他业务逻辑放到Service中,按照贫血模型设计

聚合、仓库与工厂

聚合的设计

  • 聚合表达的是真实世界中整体与部分的关系

image-20230305151538817

  • 创建订单时:将订单明细创建在订单中
  • 保存订单时:同时保存订单表与订单明细表,并放在同一个事务中
  • 查询订单时:同时查询订单表与订单明细表,并将其装配成一个订单对象

  • 整体不存在时,部分就变的没有了意义
  • 部分被封装在整体中,通过整体去操作部分,整体就是外部访问的唯一入口:聚合根
  • 增删改的业务可以采用领域驱动设计

聚合的实现

  • 仓库(Repository)

    • 系统将领域对象放在仓库,需要时通过ID到仓库中取获取;

    • 通过ID获取领域对象

      • 仓库通过ID先到缓存中进行查找,找到直接返回
      • 没找到,通知工厂,工厂调用DAO去数据库查询,然后装配成领域对象返回给仓库
      • 缓存对象,返回对象
    • 与数据访问层(DAO)区别

      扫描二维码关注公众号,回复: 16244913 查看本文章
      • 当数据保存时,由DAO负责保存,但保存的是某个单表
      • 当数据查询时,通过DAO查询,查询的是某个单表

image-20230305152915395

image-20230305154125001

  • DDD 的工厂

  • 系统要通过ID装载一个订单

    • 订单仓库将任务交给订单工厂,订单工厂分别调用订单DAO、订单明细DAO和用户DAO去进行查询
    • 将订单明细对象与用户对象,分别set到订单对象的“订单明细”与“用户”属性中
    • 订单工厂将装配好的订单对象返回给订单仓库
    • image-20230305154156437
  • 通过仓库与工厂,对原有的DAO进行一层封装在保存、装载、查询等操作中,加入聚合、装配等操作将这些操作封装起来,对上层程序屏蔽

限界上下文

将整个系统划分成许多相对独立的业务场景,在一个一个业务场景中进行领域分析与建模这样的业务场景称之为**“问题子域”**,简称“子域”。

领域驱动核心的设计思想-------将对软件的分析与设计还原到真实世界中,真实世界的业务与问题叫做“问题域”,业务规则与知识叫“业务领域知识”。

限界上下文:一个复杂系统的领域驱动设计,是以子域为中心进行领域建模,绘制出一张一张的领域模型设计

上下文地图:限界上下文之间的相互关系

  • 限界上下文内的高内聚
    • 每个限界上下文内的实现的功能都是软件变化的同一个原因的代码
  • 限界上下文间的低耦合
    • 限界上下文通过上下文地图互相调用时,通过接口进行调用

image-20230305161841356

微服务

微服务最佳实践DDD

  • 微服务设计最核心的难题是微服务的拆分,要讲究小而专的设计,要低耦合、高内聚
  • 领域驱动设计解决微服务如何拆分,实现微服务的高内聚与单一职责的问题

微服务最佳的实践是DDD

  • 从DDD开始需求分析、领域建模,逐渐建立起多个问题子域
  • 将问题子域落实到限界上下文,它们之间的关联形成上下文地图
  • 各子域落实到微服务中贫血模型或者充血模型的设计,从而在微服务之间依据上下文地图形成接口

微服务设计过程

image-20230305163102545

image-20230305164912175

  • 统一语言建模(解决需求分析困境)
    • 了解业务,从客户那里去学习,学习业务专业术语
  • 事件风暴会议
    • 在产品经理的引导下,与业务专家开始梳理当前的业务中有哪些领域事件
      • 领域事件:已经发生,需要保存的操作
      • 增删改,查询不使用DDD,使用其他的需求分析
    • 针对每一个领域事件,围绕它进行业务分析,增加各种命令与事件,进而思考与之相关的资源、外部系统与时间
    • 识别模型中可能涉及的聚合及其聚合根

微服务落地

image-20230305174234326

image-20230305174316850

image-20230305174509421

整洁架构

在DDD实现过程中,技术中台应当能够封装那些繁琐的聚合操作仓库与工厂的设计,以及相关的各种技术

image-20230305175625211

image-20230305175654229

image-20230305175716098

中台

将以往业务系统中可以复用的前台与后台代码剥离个性、提取共性,形成的共用组件。

  • 业务中台:将抽象的业务组件,做成微服务各个业务系统都可以使用
  • 技术中台:封装各个业务系统所要采用的技术框架,设计出统一的API
  • 数据中台:整理各个业务系统的数据,建立数据存储与运算的平台

大前端 + 技术中台

  • 降低业务开发的工作量,提高开发速度,降低技术门槛
  • 命令与查询职责分离模式 CQRS
    • 命令(增删改操作);采用领域驱动设计思想进行软件设计
    • 查询操作;采用事务脚本模式,即直接通过SQL语句进行查询

增删改的架构设计

image-20230305180555937

  • 单 Controller 的设计

    • 如果调用的方法有值对象,必须将值对象放在方法的第一个参数上
    • 如果要调用的方法既有值对象,又有其他参数,则值对象中的属性与其他参数放在该 Json 对象中
    • 在实际项目中需要在 Controller 前进行权限校验,来规范前端可以调用的方法;使用服务网关或者 filter 进行校验
    • image-20230305182302515
    • Controller 的流程设计
      1. 根据前端参数 bean 从Spring 中获取 Service
      2. 根据前端参数 method,通过反射获取调用方法
      3. 通过反射获取调用方法的第一个参数作为值对象
      4. 根据反射获取值对象的所有属性,从前端JSON 中获取对应属性的值,写入值对象
      5. 根据前端 JSON 获取其他参数
      6. 将值对象与其他参数,使用反射调用 Service 中的 method 方法
  • 单 DAO 的设计

    • image-20230305182522121

    • 值对象中可以有很多属性变量,但只有最终做持久化的属性变量才需要配置

    • image-20230305182920820

    • 每个 Service 在 Spring 中都是统一注入 BasicDao

      • 如果使用 DDD 的功能支持,注入通用仓库 Repository
      • 如果使用 Redis 缓存,注入 RepositoryWithCache
    • DAO 的设计

      1. 单 DAO 调用 VObjFactory.getObj(class) 获取配置信息 vObj

      2. 根据 vObj.getTable() 获取对应的表名

      3. for(Property prop: vObj.getPreperties()){

        • 通过 prop.getColumn() 获取值对象对应的字段
        • 运用反射从值对象中获得所有属性及其对应的值
        • 通过以上参数形成 SQL 语句

        }

      4. 通过 SQL 语句执行数据库操作

  • 基于这个框架进行开发,包括 Spring 的配置、MyBatis 的配置、vObj 的配置建议都采用 XML 文件的形式,而不是采用注解;方便移植

查询功能的架构设计

image-20230305183811681

image-20230305183848987

image-20230305183938777

image-20230305183957249

image-20230305184107976

image-20230305184328347

支持 DDD 的技术中台

传统 DDD 架构
  • image-20230305184614515
  • DDD 的仓库和工厂的设计介于业务领域层与基础设施层之间(接口在业务领域层,实现在基础设施层)
  • image-20230305184747960
  • image-20230305184838040
通用仓库与通用工厂设计

image-20230305184944944

  • 装载或者查询订单时,要查询订单表,填补与订单相关的订单明细与客户信息、商品信息并装配成一个对象
  • 保存订单时,不仅要保存订单表还要保存订单明细表,并将它们放到同一个事务中
  • 装饰者模式在原有的功能基础上进行扩展
  • image-20230305190005539

中…(img-ZI9ldbVe-1678023713068)]

  • [外链图片转存中…(img-nAPcOSTD-1678023713069)]
通用仓库与通用工厂设计

[外链图片转存中…(img-PYvF4Kaa-1678023713070)]

  • 装载或者查询订单时,要查询订单表,填补与订单相关的订单明细与客户信息、商品信息并装配成一个对象
  • 保存订单时,不仅要保存订单表还要保存订单明细表,并将它们放到同一个事务中
  • 装饰者模式在原有的功能基础上进行扩展
  • [外链图片转存中…(img-RMuT1ZZX-1678023713070)]

猜你喜欢

转载自blog.csdn.net/weixin_46488959/article/details/129351773