当前位置: 首页 > news >正文

DDD架构学习

DDD架构学习

前言:学习ddd架构的记录

0 前置知识

下面是一个ddd的项目的架构

com.example.project
└── order (订单业务模块 / 限界上下文)├── interfaces (用户接口层)│   ├── controller      // 接收 HTTP 请求,解析参数,返回响应│   ├── dto             // 接口层的请求/响应对象 (Request/Response DTO)│   └── assembler       // 负责 DTO 与领域对象/应用层对象的转换│├── application (应用层)│   ├── service         // 应用服务,负责业务流程编排和事务控制│   ├── command         // 命令对象 (CQRS中的C,代表写操作的入参)│   ├── query           // 查询对象 (CQRS中的Q,代表读操作的入参)│   └── event           // 应用层的事件处理或监听器│├── domain (领域层 - 核心业务逻辑)│   ├── model           // 聚合根、实体 (Entity)、值对象 (Value Object)│   ├── repository      // 仓储接口 (仅定义接口,不包含实现)│   ├── service         // 领域服务 (处理跨实体的复杂业务逻辑)│   ├── event           // 领域事件 (Domain Event,如 OrderCreatedEvent)│   └── factory         // 工厂 (封装复杂聚合的创建逻辑)│└── infrastructure (基础设施层)├── persistence     // 持久化实现│   ├── mapper      // MyBatis/JPA 的 Mapper 接口│   ├── po          // 持久化对象 (与数据库表一一对应,也叫 DO/Entity)│   └── repository  // 仓储接口的实现类 (如 OrderRepositoryImpl)├── rpc             // 调用外部微服务的 RPC Client 或防腐层 (ACL) 实现├── mq              // 消息队列的生产者/消费者实现└── config          // 该模块相关的技术配置

最大的区别就是我们的代码是根据域来组织的,像我们当前一个订单域下,需要的内容都在一起,但像我们传统的

mvc架构中,controller放在一起,service放在一起,mapper放在一起,并没有像这样进行聚合

下面来解释一下常见概念:

  1. 领域对象: 凡是承载了业务含义、封装了业务逻辑的对象,都统称为领域对象。领域对象主要有下面这两种
  2. 实体: 实体是领域模型中最基础的对象。它的核心特征是拥有唯一的身份标识 比如订单,用户,商品就是实体,而实体中还有特殊的一种实体,被称为聚合根
  3. 聚合根: 聚合根本质上是一种特殊的实体。具有全局唯一的 ID、是外部访问聚合内部对象的唯一入口、负责维护内部数据的一致性。
  4. 值对象: 值对象没有唯一的身份标识,它存在的意义是描述实体的一些特征。它的核心特征是不可变性,且通过所有属性的组合来定义相等性 是实体中属性的值,以对象去代表其值
  5. 聚合:聚合是一组高度关联的领域对象(实体和值对象)的集合。 它最重要的作用是划定一个“一致性边界”
    在这个边界内部的所有对象,必须作为一个整体来保证业务规则和数据的一致性,其中一共限界上下文中可以包含多个聚合
  6. CQRS即 命令查询职责分离 我们在过去的mvc架构中,在开发过程中经常在写crud中用一个entity来进行读操作和写操作,经常出现将全部数据读出返回给前端,可能前端只需要其中的几列数据这种情况,或者出现前端和后端共同要求entity的情况 ,在并发的读情况下也会影响写性能,所以就有了CQRS ,在ddd中,cqrs体现在写端:严格遵循 DDD,使用聚合根(Aggregate Root)实体(Entity)来保证业务规则的完整性。读端:完全绕过复杂的聚合根,直接生成扁平化的 DTOVO 存入查询库。代码职责极度清晰:写代码的人只管业务对不对,读代码的人只管查得快不快。通过读操作和写操作 解决了下面这些问题 解决了“为了查数据,被迫加载整个聚合”的性能浪费问题 解决了“聚合根代码臃肿,被各种展示需求污染”的问题解决了“复杂报表查询,需要写一堆低效联表 SQL”的问题

1. interfaces

相比我们三层架构中单独由controller 来接收请求,在ddd结构中使用interfaces 也就是用户接口层统一去做请求

接收与处理,在controller中绝对不能写业务逻辑,真正的业务逻辑由领域层中领域模型的方法和领域服务的方法

来执行,在interfaces层中,除了controller,还包含了当前接收请求需要用到的dto 和 转化用的assembler

dto很好理解,直接来接收前端传来的数据,我们放在dto里等待后续使用,在ddd架构中,有这样的一个要求

绝对不允许把“领域对象(聚合根/实体)”直接暴露给外部系统(比如直接当成 JSON 返回给前端)

所以我们直接将dto发送给后续的application的service是不行的,这里就需要用到assembler来进行转换,在收到

消息时,将dto 转换为领域对象 ,然后将该对象传入后续的 application 在application处理完后,也不能直接将

领域对象返回给前端,也是需要通过assembler来将其转化dto来返回给前端

内部其实只是进行了值拷贝罢了

2.application

application起到的作用更像是一个组织者,我们在接收到接口层的请求后,在application层中具体的service方法

中组织调用domain中的实体内部的方法和领域服务,注意此时应用层的service类似于我们mvc中的service,也是

一个接口,依赖于其实现类,在实现类内也不写业务逻辑,主要干两件事一是业务流程编排,也就是调用domain

的实体方法和领域服务,最多涉及一点格式转换,二是负责事务控制,来开启事务和提交事务

我们这里用到了CQRS的思想做了命令查询职责分离,我们看到aplication层下还有command和query ,这两部分

就是定义的数据盒子,用于在接口层使用assembler来根据读或者写将其转为query和command对象,读写走不

同的逻辑,不同的操作,比如读操作可以绕过聚合根,直接读数据库,可以不读原始数据库,可以去直接读es或

者构建好的宽表,绕过联表查询和拼接,便于提高效率和后续拓展,我们看到application层还有event ,这个

event用于事件监听,在其中可以写事件监听器,当我们在应用的主业务完成后,需要一些副业务时,就会发送一

个领域事件,这个事件被对应的事件监听器监听到了之后就会执行,事件执行分为下面四种

1. 同步非事务事件(默认) :

此时当事件发送后,主业务会出现阻塞,然后由当前主线程去执行触发的事件,执行完事件后回到主业务继续执行,监听器和发布者在同一个事务中。如果监听器里抛出了异常,整个事务会回滚。

2.异步事件

通过在监听器方法上添加 @Async 注解,并配合全局的 @EnableAsync 开启异步支持。

  • 执行特点:发布者发布事件后立刻返回,不等待监听器执行。监听器会被提交到独立的线程池中去运行。
  • 事务表现:监听器运行在全新的线程中,拥有独立的事务上下文,与原发布者的事务完全隔离。
  • 适用场景:耗时较长、非核心业务、允许有短暂延迟的场景。
  • 实战举例:用户下单成功后,发送短信/邮件通知推送大数据埋点。这些操作不影响下单主流程,异步执行可以极大地提升接口的响应速度。

3. 同步事务事件 (@TransactionalEventListener)

这是解决“事务未提交,事件就被消费”这一经典 Bug 的神器。使用 @TransactionalEventListener 替代 @EventListener

  • 执行特点:依然是同步执行(阻塞主线程),但它的触发时机与数据库事务的生命周期绑定。

  • 事务表现

    :它可以根据

    phase
    

    属性,精准控制在事务的哪个阶段执行。

    • AFTER_COMMIT(最常用):只有当主业务的事务成功提交后,监听器才会执行。
    • AFTER_ROLLBACK:仅在事务回滚后执行。
    • BEFORE_COMMIT:在事务提交前执行(可用于最终校验)。
  • 适用场景:业务逻辑依赖主事务必须成功,且需要实时执行。

  • 实战举例:用户下单成功后,需要更新 Redis 缓存中的商品库存。你必须确保订单已经稳稳地存入数据库(事务提交)后,才能去改缓存。如果用普通同步事件,万一事件执行了但主事务因为其他原因回滚了,缓存和数据库的数据就不一致了。

需要注意,对于默认的事务传播方式来说,此时主业务和副业务是事务不隔离的,虽然可以设置为主业务提交后监

听器执行,但是此时数据落库了,事务上下文未结束,还是处于一个事务,如果副业务报错,主业务会报错但数据已经落库了,就会导致不一致,如果想要避免这种情况就需要开启

propagation = Propagation.REQUIRES_NEW

4. 异步事务事件 (@Async + @TransactionalEventListener)

这是生产环境中最完美的组合拳,将上面两种模式结合:同时加上 @Async@TransactionalEventListener(phase = AFTER_COMMIT)

  • 执行特点:主线程发布事件后立刻返回(不阻塞)。监听器会在主事务成功提交后,被异步提交到线程池中执行。
  • 事务表现:监听器拥有独立的事务,且绝对保证了主业务的数据已经落库。
  • 适用场景:既要求主业务数据强一致,又要求高性能、低延迟的解耦操作。
  • 实战举例:电商下单成功后,需要调用第三方风控接口生成复杂的运营报表。这些操作既不能影响下单速度(需要异步),又必须建立在订单真实存在的基础上(需要事务提交后)。

这种情况就是目前最好用的

http://www.jsqmd.com/news/766749/

相关文章:

  • 多层构建导致镜像过大 Docker Compose 如何优化 build 上下文
  • Pycharm效率翻倍秘籍:从文件模板、字体缩放快捷键到中文插件完整配置流程
  • 手机号查QQ号终极指南:30秒找回遗忘的QQ账号
  • 大语言模型事实核查与时效性评估实战
  • 如何在 openclaw 中快速配置 taotoken 聚合端点实现多模型调用
  • 知识竞赛软件免费版 vs 付费版
  • Docker 学习篇(一)| 认识 Docker
  • 2026 年机房防静电地板全国普及:安全价值与全国化供应格局解析 - 小艾信息发布
  • N_m3u8DL-RE架构深度解析:现代流媒体下载引擎的设计哲学与技术实现
  • Windows预览版一键退出指南:如何快速恢复系统稳定性的完整教程
  • 别让布线毁了信号!深入PCIe链路训练:Polarity Inversion检测与纠正全流程解析
  • GTA5线上小助手:重新定义你的洛圣都游戏体验
  • 别再死磕标准库了!STM32CubeMX+HAL库开发实战,从零到点亮LED(附避坑指南)
  • Allegro 16.6实战:为了信号完整性和良率,我这样设置PCB无盘工艺
  • 内容创作平台集成Taotoken实现多模型文章辅助生成与润色
  • 2026年值得关注的AI大模型接口中转站推荐,这五家让你的开发之路更顺畅
  • 工控机上的游戏手柄:Ubuntu 20.04连接Xbox/北通手柄完整配置与避坑指南
  • SQL 入门 13:SQL 存储过程与函数:封装逻辑与参数处理
  • qmc-decoder终极指南:三步解锁QQ音乐加密文件,实现跨平台音乐自由
  • Ubuntu 18.04离线环境升级GLIBC踩坑记:从报错到成功,我绕过了哪些弯路?
  • 【软考高级架构】案例题考前突击12:软件架构的演化
  • 3分钟搞定M3U8视频下载:告别命令行,拥抱图形化下载神器
  • Go语言构建Webhook转发桥梁:解决内网穿透,实现自动化流程
  • KMS激活脚本终极指南:5分钟免费激活Windows和Office的完整教程
  • Linux USB Gadget配置避坑指南:从DTS到Function驱动的完整流程解析
  • Sunshine游戏串流服务器:开源自托管方案重塑跨设备游戏体验
  • 借助Taotoken模型广场为不同任务选择性价比最优的大模型
  • 2026年华为云简洁教程:OpenClaw怎么搭建及大模型API Key、Skill配置全攻略
  • 开源神器LinkSwift:一键获取九大网盘真实下载链接的终极指南
  • 2026奇点大会闭门报告流出(AISMM V2.1实测数据包):92.7%头部AI厂商已启动内部对标,你还在用LlamaScore?