领域驱动设计(DDD)四 订单管理系统实践步骤
以下是基于 领域驱动设计(DDD) 的订单管理系统实践步骤,系统功能主要包括订单的创建、更新、查询和状态管理,采用 Spring Boot 框架进行实现。
1. 需求分析
订单管理系统的基本功能:
- 订单创建:用户下单创建订单。
- 订单状态更新:更新订单状态(如已支付、已发货、已完成)。
- 订单查询:支持按订单 ID 或用户查询订单信息。
- 库存管理(简化版):订单创建时检查并扣减库存。
2. 领域划分与限界上下文
限界上下文
- 订单上下文(Order Context):负责订单的生命周期管理。
- 库存上下文(Inventory Context):负责商品库存的管理。
模块划分
src/main/java/com/example/order
├── application # 应用层:处理用例逻辑
│ ├── service # 应用服务
│ └── dto # 数据传输对象
├── domain # 领域层:业务逻辑
│ ├── model # 领域模型(实体、值对象、聚合)
│ ├── repository # 仓储接口
│ ├── service # 领域服务
│ └── event # 领域事件
├── infrastructure # 基础设施层:技术实现
│ ├── repository # 仓储实现
│ └── event # 事件机制
└── interfaces # 接口层:用户交互├── controller # REST 接口└── vo # 视图对象
3. 代码实现
3.1 领域层设计
领域模型
(1) 订单实体
public class Order {private Long id; // 订单IDprivate String customerId; // 用户IDprivate List<OrderItem> items; // 订单项private OrderStatus status; // 订单状态// 创建订单public Order(String customerId, List<OrderItem> items) {this.customerId = customerId;this.items = items;this.status = OrderStatus.CREATED;}// 更新订单状态public void updateStatus(OrderStatus status) {if (this.status.canTransitionTo(status)) {this.status = status;} else {throw new IllegalStateException("非法状态转换");}}// 计算总金额public BigDecimal calculateTotal() {return items.stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add);}// Getters and Setters
}
(2) 订单项值对象
public class OrderItem {private Long productId; // 商品IDprivate int quantity; // 数量private BigDecimal price; // 单价// 构造方法public OrderItem(Long productId, int quantity, BigDecimal price) {this.productId = productId;this.quantity = quantity;this.price = price;}// Getters and Setters
}
(3) 订单状态值对象
public enum OrderStatus {CREATED, PAID, SHIPPED, COMPLETED;// 状态转换规则public boolean canTransitionTo(OrderStatus newStatus) {return switch (this) {case CREATED -> newStatus == PAID;case PAID -> newStatus == SHIPPED;case SHIPPED -> newStatus == COMPLETED;default -> false;};}
}
3.2 应用层设计
订单服务
@Service
public class OrderApplicationService {private final OrderRepository orderRepository;private final InventoryService inventoryService;public OrderApplicationService(OrderRepository orderRepository, InventoryService inventoryService) {this.orderRepository = orderRepository;this.inventoryService = inventoryService;}// 创建订单public Long createOrder(String customerId, List<OrderItemDto> itemDtos) {// 转换 DTO 为领域模型List<OrderItem> items = itemDtos.stream().map(dto -> new OrderItem(dto.getProductId(), dto.getQuantity(), dto.getPrice())).toList();// 检查库存items.forEach(item -> inventoryService.reserveStock(item.getProductId(), item.getQuantity()));// 创建订单Order order = new Order(customerId, items);orderRepository.save(order);return order.getId();}// 更新订单状态public void updateOrderStatus(Long orderId, OrderStatus status) {Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("订单不存在"));order.updateStatus(status);orderRepository.save(order);}// 查询订单public OrderDto getOrderById(Long orderId) {Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("订单不存在"));return OrderDto.fromDomain(order);}
}
3.3 基础设施层设计
仓储实现
@Repository
public class JpaOrderRepository implements OrderRepository {private final SpringDataOrderJpaRepository jpaRepository;public JpaOrderRepository(SpringDataOrderJpaRepository jpaRepository) {this.jpaRepository = jpaRepository;}@Overridepublic void save(Order order) {jpaRepository.save(order);}@Overridepublic Optional<Order> findById(Long id) {return jpaRepository.findById(id);}
}
事件机制
@Component
public class DomainEventPublisher {private final ApplicationEventPublisher eventPublisher;public DomainEventPublisher(ApplicationEventPublisher eventPublisher) {this.eventPublisher = eventPublisher;}public void publish(Object event) {eventPublisher.publishEvent(event);}
}
3.4 接口层设计
订单控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {private final OrderApplicationService orderService;public OrderController(OrderApplicationService orderService) {this.orderService = orderService;}@PostMappingpublic ResponseEntity<Long> createOrder(@RequestBody OrderRequest request) {Long orderId = orderService.createOrder(request.getCustomerId(), request.getItems());return ResponseEntity.status(HttpStatus.CREATED).body(orderId);}@PutMapping("/{orderId}/status")public ResponseEntity<Void> updateStatus(@PathVariable Long orderId, @RequestParam OrderStatus status) {orderService.updateOrderStatus(orderId, status);return ResponseEntity.ok().build();}@GetMapping("/{orderId}")public ResponseEntity<OrderDto> getOrderById(@PathVariable Long orderId) {return ResponseEntity.ok(orderService.getOrderById(orderId));}
}
3.5 示例数据传输对象(DTO)
public class OrderDto {private Long id;private String customerId;private List<OrderItemDto> items;private OrderStatus status;public static OrderDto fromDomain(Order order) {return new OrderDto(order.getId(), order.getCustomerId(),order.getItems().stream().map(OrderItemDto::fromDomain).toList(),order.getStatus());}
}
4. 运行与测试
数据库表结构(MySQL 示例)
CREATE TABLE orders (id BIGINT AUTO_INCREMENT PRIMARY KEY,customer_id VARCHAR(255),status VARCHAR(50)
);CREATE TABLE order_items (id BIGINT AUTO_INCREMENT PRIMARY KEY,order_id BIGINT,product_id BIGINT,quantity INT,price DECIMAL(10,2),FOREIGN KEY (order_id) REFERENCES orders(id)
);
测试示例
-
创建订单
POST /api/orders {"customerId": "123","items": [{ "productId": 1, "quantity": 2, "price": 50.00 },{ "productId": 2, "quantity": 1, "price": 100.00 }] } -
更新订单状态
PUT /api/orders/1/status?status=PAID -
查询订单
GET /api/orders/1
这套设计是 DDD 的小型实践示例,后续可以扩展领域事件、复杂聚合和限界上下文的实现。
相关文章:
领域驱动设计(DDD)四 订单管理系统实践步骤
以下是基于 领域驱动设计(DDD) 的订单管理系统实践步骤,系统功能主要包括订单的创建、更新、查询和状态管理,采用 Spring Boot 框架进行实现。 1. 需求分析 订单管理系统的基本功能: 订单创建:用户下单创…...
leetcode 面试经典 150 题:简化路径
链接简化路径题序号71题型字符串解法栈难度中等熟练度✅✅✅ 题目 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为 更加简洁的规范路径。 在 Unix 风格的文件系统中规则如下…...
基于 STM32 的智能农业温室控制系统设计
1. 引言 随着农业现代化的发展,智能农业温室控制系统对于提高农作物产量和质量具有重要意义。该系统能够实时监测温室内的环境参数,如温度、湿度、光照强度和土壤湿度等,并根据这些参数自动调节温室设备,如通风扇、加热器、加湿器…...
【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用
前言 🌟🌟本期讲解关于spring 事务传播机制介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…...
【Postgres_Python】使用python脚本将多个PG数据库合并为一个PG数据库
需要合并的多个PG数据库表个数和结构一致,这里提供一种思路,选择sql语句insert插入的方式进行,即将其他PG数据库的每个表内容插入到一个PG数据库中完成数据库合并 示例代码说明: 选择一个数据库导出表结构为.sql文件(…...
Tailwind CSS v4.0 发布
Holy shit its actually done ! 1 月 22 日,Tailwind CSS 正式发布了 4.0 版本,针对性能和灵活性进行了优化,重新构想了配置和定制体验,并充分利用了 Web 平台提供的最新进展。 新的高性能引擎- 完整构建速度提高 5 …...
pandas基础:文件的读取和写入
文件的读取和写入 读取csv文件 csv文件: name,age,city Alice,25,New York Bob,30,Los Angelesread_csv(filename) header:如 何处理文件的第一行。header0将第一行作为列名,headerNone表示文件中没有列名,所有行都是数据。 im…...
【MySQL — 数据库增删改查操作】深入解析MySQL的create insert 操作
数据库CRUD操作 1 CRUD简介 CURD是对数据库中的记录进行基本的增删改查操作: 2. Create 新增 语法 INSERT [INTO] table_name[(column [,column] ...)] VALUES(value_list)[,(value_list)] ... # value 后面的列的个数和类型,要和表结构匹配…...
每日OJ_牛客_小红的子串_滑动窗口+前缀和_C++_Java
目录 牛客_小红的子串_滑动窗口前缀和 题目解析 C代码 Java代码 牛客_小红的子串_滑动窗口前缀和 小红的子串 描述: 小红拿到了一个长度为nnn的字符串,她准备选取一段子串,满足该子串中字母的种类数量在[l,r]之间。小红想知道&…...
HTTP 配置与应用(局域网)
想做一个自己学习的有关的csdn账号,努力奋斗......会更新我计算机网络实验课程的所有内容,还有其他的学习知识^_^,为自己巩固一下所学知识,下次更新HTTP 配置与应用(不同网段)。 我是一个萌新小白…...
ultralytics 是什么?
ultralytics 是一个用于计算机视觉任务的 Python 库,专注于提供高效、易用的目标检测、实例分割和图像分类工具。它最著名的功能是实现 YOLO(You Only Look Once) 系列模型,特别是最新的 YOLOv8。 1. YOLO 是什么? YO…...
AI竞争:从技术壁垒到用户数据之争
标题:AI竞争:从技术壁垒到用户数据之争 文章信息摘要: AI市场呈现开放模型与封闭模型并存的双轨发展态势,但核心竞争力已从模型技术转向用户数据积累和使用习惯培养。商业模式正在多元化发展,从早期的价格战转向subsc…...
MySQL 主从复制(单组传统复制,GTID复制。双主复制)
案例环境 单组复制 master: 192.168.180.143 slave01:192.168.180.144 双组复制 master01:192.168.180.143 master02:192.168.180.144 案例过程 准备工作 关闭所有防火墙 setenforce 0 && systemctl stop firewa…...
python学opencv|读取图像(四十)掩模:三通道图像的局部覆盖
【1】引言 前序学习了使用numpy创建单通道的灰色图像,并对灰色图像的局部进行了颜色更改,相关链接为: python学opencv|读取图像(九)用numpy创建黑白相间灰度图_numpy生成全黑图片-CSDN博客 之后又学习了使用numpy创…...
vue3 中如何监听 props 中的值的变化
在 Vue 3 中,你可以使用 watch 函数来监听组件的 props 值的变化。watch 函数允许你观察一个或多个响应式数据源,并在这些数据源发生变化时执行回调函数。 以下是一个示例,展示了如何在 Vue 3 中使用 watch 来监听 props 中的值的变化&#…...
Scrapy之一个item包含多级页面的处理方案
目标 在实际开发过程中,我们所需要的数据往往需要通过多个页面的数据汇总得到,通过列表获取到的数据只有简单的介绍。站在Scrapy框架的角度来看,实际上就是考虑如何处理一个item包含多级页面数据的问题。本文将以获取叶子猪网站的手游排行榜及…...
hive 自动检测、自动重启、记录检测日志、自动清理日志
最终效果 定时检测hive运行状态,进程不存在或者进程存在但是不监听端口的hiveserver2,自动重新拉起每次检测脚本执行的日志都会保存在log目录下.check文件,每一个月一个文件每月15日,删除2月前的检测日志开启hive自带日志输出后&…...
HFSS同轴替换波端口
波端口仿真正常 将波端口换成内径内径0.3mm外径0.6mm同轴之后 结果很不对 换成下面的尺寸就好了...
【2024年华为OD机试】 (C卷,100分)- 素数之积(JavaScriptJava PythonC/C++)
一、问题描述 RSA 因数分解问题 题目描述 RSA 加密算法在网络安全世界中无处不在,它利用了极大整数因数分解的困难度。数据越大,安全系数越高。给定一个 32 位正整数,请对其进行因数分解,找出是哪两个素数的乘积。 输入描述 …...
【C++模板】:如何判断自定义类型是否实现某个函数
一、引子 偶尔我们会面对这样的尴尬的场景,我们需要显示的去判断在某个自定义类型中,是否已经提供了我们期待的API接口,以避免产生“莫须有”的错误。阁下该如何破解此问题! 这里,直接给出一种通用的方法,…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
