DDD领域驱动与传统CRUD

DDD 是一套 应对复杂业务系统 的设计方法论,核心是 让代码直接映射业务逻辑,避免技术实现与业务需求脱节。
关键区别

  • 传统开发:根据数据库表写 CRUD(技术驱动)。
  • DDD:根据业务行为建模(业务驱动)。

举例

  • 传统方式:设计 orders 表 → 写 OrderDAO → 实现增删改查。
  • DDD 方式:先分析业务如何描述“订单”(如“下单”、“取消”、“支付超时”)→ 再建模为 Order 对象的行为。

贫血模型与充血模型

本质特征

特征贫血模型充血模型
业务逻辑存放位置Service层领域对象(Entity/Value Object)内部
领域对象职责仅作为数据载体(DTO)封装数据和行为
代码表现大量Getter/Setter,无业务方法包含业务方法
适用场景简单CRUD系统复杂业务系统(DDD推荐)

代码实现对比

贫血模型实现

  • Order 只是数据的容器,业务逻辑散落在 Service 中。
  • 当业务复杂时,Service 会变成“上帝类”(God Class)。
// 1. 贫血的Order类(仅有数据)
public class Order {private String orderId;private String productId;private int quantity;private String status;// 只有Getter/Setter,无业务逻辑public String getOrderId() { return orderId; }public void setOrderId(String orderId) { this.orderId = orderId; }// 其他Getter/Setter省略...
}// 2. 业务逻辑全在Service中
@Service
public class OrderService {public void createOrder(String productId, int quantity) {Order order = new Order();order.setOrderId(UUID.randomUUID().toString());order.setProductId(productId);order.setQuantity(quantity);order.setStatus("CREATED");// 校验逻辑也在Service中if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}orderRepository.save(order);}
}

充血模型实现

  • Order 自己负责业务规则(如创建校验、状态转换)。
  • 业务逻辑高内聚,Service层变薄。
// 1. 充血的Order类(封装数据和行为)
public class Order {private String orderId;private String productId;private int quantity;private OrderStatus status; // 枚举// 私有构造方法,强制通过工厂方法创建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 静态工厂方法(封装创建逻辑)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 领域行为public void cancel() {if (this.status == OrderStatus.PAID) {throw new IllegalStateException("Paid order cannot be cancelled");}this.status = OrderStatus.CANCELLED;}
}// 2. 应用层Service(仅协调流程)
@Service
public class OrderAppService {public void createOrder(String productId, int quantity) {Order order = Order.create(productId, quantity); // 调用领域对象orderRepository.save(order);}
}

DDD实战

架构图

架构设计

src/
├── presentation/    # 用户接口层
├── application/     # 应用层
├── domain/          # 领域层
│   ├── model/       # 领域模型(实体、值对象等)
│   └── repository/  # 仓储接口
└── infrastructure/  # 基础设施层(简单实现)

代码示例

(1) 用户接口层(Presentation Layer)

处理HTTP请求,接收用户输入。

// presentation/OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {private final OrderAppService orderAppService; // 依赖应用层@PostMappingpublic ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {orderAppService.createOrder(request.toCommand()); // 转换为命令对象return ResponseEntity.ok("Order created");}
}// DTO:用户请求参数
public class CreateOrderRequest {private String productId;private int quantity;// 转换为应用层命令对象public CreateOrderCommand toCommand() {return new CreateOrderCommand(productId, quantity);}
}

(2) 应用层(Application Layer)

协调领域对象完成用例,不包含业务逻辑

// application/OrderAppService.java
@Service
public class OrderAppService {private final OrderRepository orderRepository; // 依赖领域层仓储public void createOrder(CreateOrderCommand command) {// 1. 调用领域层创建订单Order order = Order.create(command.getProductId(), command.getQuantity());// 2. 通过仓储保存聚合根orderRepository.save(order);// 3. 可发布领域事件(此处省略)}
}// 应用层命令对象
public class CreateOrderCommand {private String productId;private int quantity;// 构造方法、getter省略...
}

(3) 领域层(Domain Layer)

核心业务逻辑,包含实体、值对象、仓储接口等。

3.1 实体(Entity)与聚合根
// domain/model/Order.java
public class Order {private String orderId;      // 唯一标识private String productId;private int quantity;private OrderStatus status;  // 枚举:CREATED, PAID, CANCELLED等// 私有构造方法,强制通过工厂方法创建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 工厂方法(封装创建逻辑)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 领域行为public void cancel() {this.status = OrderStatus.CANCELLED;}
}
3.2 仓储接口(Repository)
// domain/repository/OrderRepository.java
public interface OrderRepository {void save(Order order);// 其他查询方法省略...
}

(4) 基础设施层(Infrastructure Layer)

实现领域层定义的仓储接口(如数据库操作)。

// infrastructure/persistence/OrderRepositoryImpl.java
@Repository
public class OrderRepositoryImpl implements OrderRepository {// 模拟数据库(实际可用JPA/MyBatis等)private Map<String, Order> orders = new HashMap<>();@Overridepublic void save(Order order) {orders.put(order.getOrderId(), order);System.out.println("Order saved: " + order.getOrderId());}
}

调用流程图

对比传统CRUD

DDD传统CRUD
先设计Order的行为方法直接操作orders
业务逻辑在领域层业务逻辑散落在Service中
通过聚合根保证一致性需手动校验外键约束

充血模型特点

  1. 禁止Setter:通过构造方法或工厂方法创建对象。
  2. 领域方法命名:使用业务语言(如 cancel() 而非 setStatus("CANCELLED"))。
  3. 避免“贫血”的充血模型
    • 错误示例:在 Order 中加了方法,但核心逻辑仍在Service中。

常见误区

  • ❌ 认为充血模型等于“把所有代码塞进Entity”
    • 跨聚合逻辑应放在领域服务(Domain Service)中。
  • ❌ 忽视聚合根的一致性边界
    • 例如订单和库存属于不同聚合,不能直接在 Order 中修改库存,应通过领域事件解耦。

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

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

相关文章

20. git diff

基本概述 git diff的作用是&#xff1a;比较代码差异 基本用法 1.工作区 VS 暂存区 git diff [file]2.暂存区 VS 最新提交 git diff --staged [file] # 或 git diff --cached [file]3.工作区 VS 最新提交 git diff HEAD [file]高级用法 1.比较两个提交间的差异 git dif…

大模型面经 | 春招、秋招算法面试常考八股文附答案(五)

大家好,我是皮先生!! 今天给大家分享一些关于大模型面试常见的面试题,希望对大家的面试有所帮助。 往期回顾: 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题一) 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题二) 大模型面经 | 春招、秋招算法…

Sql刷题日志(day5)

面试&#xff1a; 1、从数据分析角度&#xff0c;推荐模块怎么用指标衡量&#xff1f; 推荐模块主要目的是将用户进行转化&#xff0c;所以其主指标是推荐的转化率推荐模块的指标一般都通过埋点去收集用户的行为并完成相应的计算而形成相应的指标数据&#xff0c;而这里的驱动…

封装 element-ui 二次弹框

author 封装 element-ui 弹框 param text 文本内容 &#xff08;不传默认显示 确定执行此操作吗&#xff1f; &#xff09; param type 弹框类型&#xff08;不传默认warning类型&#xff09; param title 弹框标题&#xff08;不传默认显示 提示 &#xff09; export fun…

【Rust 精进之路之第12篇-生命周期·入门】为何需要与显式标注 (`‘a`):让编译器读懂引用的“有效期”

系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025-04-20 引言:悬垂引用的“幽灵”与编译器的“侦探” 在前面的章节中,我们深入学习了 Rust 的所有权系统,以及如何通过引用 (& 和 &mut) 进行借用,从而在不转移所有权的情况下安…

[密码学实战]CTF竞赛高频加密与解密技术详解

CTF竞赛高频加密与解密技术详解 一、CTF加密体系全景图 在CTF密码学挑战中&#xff0c;加解密技术主要分为四大战域&#xff1a; #mermaid-svg-lmm07BXqYAGYjymI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lm…

docker.desktop下安装普罗米修斯prometheus、grafana并看服务器信息

目标 在docker.desktop下先安装这三种组件,然后显示当前服务的CPU等指标。各种坑已踩,用的是当前时间最新的镜像 核心关系概述 组件角色依赖关系Prometheus开源监控系统,负责 数据采集、存储、查询及告警。依赖 Node-Exporter 提供的指标数据。Node-Exporter专用的 数据采集…

《MySQL:MySQL表的内外连接》

表的连接分为内连接和外连接。 内连接 内连接实际上就是利用where子句对两种表形成的笛卡尔积进行筛选&#xff0c;之前的文章中所用的查询都是内连接&#xff0c;也是开发中使用的最多的连接查询。 select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件&#xff1…

实现SpringBoot底层机制【Tomcat启动分析+Spring容器初始化+Tomcat 如何关联 Spring容器】

下载地址&#xff1a; https://download.csdn.net/download/2401_83418369/90675207 一、搭建环境 创建新项目 在pom.xml文件中导入依赖 &#xff08;一定要刷新Maven&#xff09;排除内嵌的Tomcat&#xff0c;引入自己指定的Tomcat <?xml version"1.0" enco…

从零开始构建微博爬虫:实现自动获取并保存微博内容

从零开始构建微博爬虫&#xff1a;实现自动获取并保存微博内容 前言 在信息爆炸的时代&#xff0c;社交媒体平台已经成为信息传播的重要渠道&#xff0c;其中微博作为中国最大的社交媒体平台之一&#xff0c;包含了大量有价值的信息和数据。对于研究人员、数据分析师或者只是…

Uniapp微信小程序:轻松获取用户头像和昵称

参考文献&#xff1a;Uniapp微信小程序&#xff1a;轻松获取用户头像和昵称-百度开发者中心 (baidu.com) uni.login({ provider: weixin, success: function (loginRes) { console.log(loginRes.authResult); // 打印登录凭证 // 使用登录凭证获取用户信息 uni.getUserInfo({ …

【自然语言处理与大模型】大模型(LLM)基础知识③

&#xff08;1&#xff09;大模型的“7B”是什么意思&#xff1f; "B"通常代表“Billion”&#xff0c;即十亿。因此&#xff0c;当提到7B时&#xff0c;指的是该模型拥有7 billion&#xff08;70亿&#xff09;个参数。 &#xff08;2&#xff09;模型后面标的“ins…

聊聊自动化用例的维护

自动化测试中的农药悖论&#xff1a;为何长期维护至关重要 自动化测试常被视为"一次编写&#xff0c;永久有效"的解决方案&#xff0c;但随着时间的推移&#xff0c;即使设计最精良的测试套件也会逐渐失效。这种现象被称为农药悖论&#xff08;Pesticide Paradox&am…

微帧Visionular斩获NAB Show 2025年度产品奖

在本月刚结束的NAB Show 2025展会上&#xff0c;全球领先的视频编码与AI超高清服务提供商微帧Visionular大放异彩&#xff0c;其核心产品AI-driven Video Compression&#xff08;AI视频智能编码引擎&#xff09;不仅在展会中吸引了众多行业目光&#xff0c;更凭借其卓越的编码…

idea中运行groovy程序报错

我的项目是使用的 gradle 构建的。 在 idea 中运行Groovy的面向对象程序报错如下&#xff1a; Execution failed for task :Person.main(). > Process command G:/Program Files/jdk-17/jdk-17.0.12/bin/java.exe finished with non-zero exit value 1* Try: Run with --s…

【自然语言处理与大模型】个人使用LLaMA Factory微调的记录

一、魔塔社区免费服务器如何使用webui微调&#xff1f; 一上来我就得先记录一下&#xff0c;使用魔塔社区的免费服务器的时候&#xff0c;因为没有提供ssh而导致无法看到webui的遗憾如何解决的问题&#xff1f; 执行命令 如果点这个链接无法弹出微调的webui&#xff0c;则可以在…

【官方正版,永久免费】Adobe Camera Raw 17.2 win/Mac版本 配合Adobe22-25系列软

Adobe Camera Raw 2025 年 2 月版&#xff08;版本 17.2&#xff09;。目前为止最新版新版已经更新2个月了&#xff0c;我看论坛之前分享的还是2024版&#xff0c;遂将新版分享给各位。 Adobe Camera Raw&#xff0c;支持Photoshop&#xff0c;lightroom等Adobe系列软件&#…

leetcode:1295. 统计位数为偶数的数字(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums&#xff0c;请你返回其中位数为 偶数 的数字的个数。 示例 1&#xff1a; 输入&#xff1a;nums [12,345,2,6,7896] 输出&#xff1a;2 解释&#xff1a; 12 是 2 位数字&#xff08;位数为偶数&#xff09; 345 是 3 位数字&…

使用Handsontable实现动态表格和下载表格

1.效果 2.实现代码 首先要加载Handsontable&#xff0c;在示例中我是cdn的方式引入的&#xff0c;vue的话需要下载插件 let hot null;var exportPlugin null;function showHandsontable(param) {const container document.getElementById("hot-container");// 如果…

2.1 基于委托的异步编程方法

基于委托的异步编程模型是 .NET 早期版本中实现异步操作的一种方式&#xff0c;主要通过 BeginInvoke 和 EndInvoke 方法来实现。这种基于委托的异步模式已被 Task 和 async/await 模式取代&#xff0c;但在维护旧代码时仍可能遇到这种模式。 委托的方法中&#xff1a;Invoke用…