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

DDD-031:案例:电商订单系统 DDD 建模

DDD-031:案例:电商订单系统 DDD 建模

本章导读

本章通过一个完整的电商订单系统案例,展示 DDD 从需求分析、领域建模到代码实现的完整过程。我们将深入探讨订单聚合的设计、状态管理、领域事件的应用,以及分层架构的具体实现。

学习目标

  1. 掌握从需求到领域模型的建模过程
  2. 学会订单聚合的设计与实现
  3. 理解订单状态机的设计方法

前置知识

  • DDD 聚合、实体、值对象基础
  • DDD 领域事件机制
  • Spring Boot 开发基础

阅读时长

约 60-75 分钟


【案例背景】电商订单系统

一、业务需求分析

1.1 核心业务流程

订单生命周期: ┌──────────────────────────────────────────────────────────────────┐ │ 订单状态流转图 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 创建订单 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ CREATED (待支付) ││ │ │ - 用户已下单,等待支付 ││ │ │ - 可取消、可支付 ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ │ │ 支付成功 用户取消 │ │ │ │ │ │ ▼ ▼ │ │ ┌───────────────────────┐ ┌───────────────────────┐ │ │ │ PAID (已支付) │ │ CANCELLED (已取消) │ │ │ │ - 等待发货 │ │ - 订单已取消 │ │ │ │ - 可取消、可发货 │ │ - 可申请退款 │ │ │ └───────────────────────┘ └───────────────────────┘ │ │ │ │ │ 商家发货 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ SHIPPED (已发货) ││ │ │ - 等待用户确认收货 ││ │ │ - 可确认收货、可申请退款 ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ │ 用户确认 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ DELIVERED (已收货) ││ │ │ - 交易完成 ││ │ │ - 可评价、可申请退款 ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ └──────────────────────────────────────────────────────────────────┘

1.2 核心业务规则

规则编号规则描述适用状态
R1订单项不能为空创建时
R2订单金额 = 订单项单价 × 数量之和创建时
R3只有待支付状态可以支付CREATED
R4已支付或待支付状态可以取消CREATED, PAID
R5已发货状态可以确认收货SHIPPED
R6每个订单项数量不能超过库存创建时

二、领域建模过程

2.1 识别聚合

订单上下文聚合识别: ┌────────────────────────────────────────────────────────────────┐ │ Order Context │ ├────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Order 聚合 │ │ │ │ │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ │ │ Order │───│ OrderItem │ │ Shipping │ │ │ │ │ │ 聚合根 │ │ 内部实体 │ │ Address │ │ │ │ │ │ │ │ │ │ 值对象 │ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ │ │ │ │ 边界: │ │ │ │ - Order 是聚合根 │ │ │ │ - OrderItem 是内部实体 │ │ │ │ - ShippingAddress 是值对象 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 引用其他聚合(只持有 ID): │ │ - CustomerId:客户聚合引用 │ │ - ProductId:商品聚合引用 │ │ - PaymentId:支付聚合引用 │ │ │ └────────────────────────────────────────────────────────────────┘

2.2 实体与值对象设计

Order 聚合内部结构: ┌────────────────────────────────────────────────────────────────┐ │ Order │ │ (Aggregate Root) │ ├────────────────────────────────────────────────────────────────┤ │ - id: OrderId // 聚合根标识 │ │ - customerId: CustomerId // 客户引用(ID) │ │ - items: List<OrderItem> // 订单项列表 │ │ - status: OrderStatus // 订单状态 │ │ - totalAmount: Money // 订单金额 │ │ - shippingAddress: Address // 收货地址 │ │ - paymentId: PaymentId // 支付引用(ID) │ │ - paidAt: Instant // 支付时间 │ │ - shippedAt: Instant // 发货时间 │ │ - deliveredAt: Instant // 收货时间 │ │ - cancelledAt: Instant // 取消时间 │ │ - cancelReason: String // 取消原因 │ ├────────────────────────────────────────────────────────────────┤ │ + create(customerId, items, address): Order │ │ + pay(paymentId): void │ │ + ship(shippingId): void │ │ + confirmDelivery(): void │ │ + cancel(reason): void │ │ + addItem(item): void │ │ + removeItem(itemId): void │ └────────────────────────────────────────────────────────────────┘ │ │ 包含 ▼ ┌────────────────────────────────────────────────────────────────┐ │ OrderItem │ │ (Entity) │ ├────────────────────────────────────────────────────────────────┤ │ - id: OrderItemId │ │ - productId: ProductId // 商品引用(ID) │ │ - productName: String // 商品名称快照 │ │ - productImage: String // 商品图片快照 │ │ - unitPrice: Money // 单价快照 │ │ - quantity: int // 数量 │ │ - subtotal: Money // 小计 │ ├────────────────────────────────────────────────────────────────┤ │ + getSubtotal(): Money │ │ + updateQuantity(quantity): void │ └────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────┐ ┌─────────────────────────────┐ │ Address │ │ Money │ │ (Value Object) │ │ (Value Object) │ ├─────────────────────────────────┤ ├─────────────────────────────┤ │ - province: String │ │ - amount: BigDecimal │ │ - city: String │ │ - currency: Currency │ │ - district: String │ ├─────────────────────────────┤ │ - detail: String │ │ + add(other): Money │ │ - postalCode: String │ │ + multiply(factor): Money │ │ - receiver: String │ │ + subtract(other): Money │ │ - phone: String │ └─────────────────────────────┘ ├─────────────────────────────────┤ │ + getFullAddress(): String │ └─────────────────────────────────┘

三、代码实现详解

3.1 项目结构

order-context/ ├── domain/ # 领域层 │ ├── model/ │ │ ├── order/ │ │ │ ├── Order.java # 订单聚合根 │ │ │ ├── OrderItem.java # 订单项实体 │ │ │ ├── OrderStatus.java # 订单状态枚举 │ │ │ └── OrderFactory.java # 订单工厂 │ │ └── shared/ │ │ ├── Money.java # 金额值对象 │ │ └── Address.java # 地址值对象 │ ├── event/ │ │ ├── OrderCreatedEvent.java │ │ ├── OrderPaidEvent.java │ │ ├── OrderShippedEvent.java │ │ └── OrderCancelledEvent.java │ ├── repository/ │ │ └── OrderRepository.java # 仓储接口 │ └── service/ │ └── OrderDomainService.java # 领域服务 ├── application/ # 应用层 │ ├── OrderApplicationService.java │ ├── command/ │ │ ├── CreateOrderCommand.java │ │ ├── PayOrderCommand.java │ │ └── CancelOrderCommand.java │ └── event/ │ └── OrderEventHandler.java ├── infrastructure/ # 基础设施层 │ ├── persistence/ │ │ ├── JpaOrderRepository.java │ │ ├── OrderPo.java # 持久化对象 │ │ └── OrderRepositoryImpl.java │ └── messaging/ │ └── OrderEventPublisher.java └── interface/ # 接口层 ├── controller/ │ └── OrderController.java ├── dto/ │ ├── CreateOrderRequest.java │ └── OrderResponse.java └── assembler/ └── OrderAssembler.java

3.2 订单聚合实现

// ✅ 订单聚合根实现packagecom.example.order.domain.model.order;publicclassOrderextendsAggregateRoot<OrderId>{// 状态privateOrderStatusstatus;privateCustomerIdcustomerId;privateList<OrderItem>items;privateMoneytotalAmount;privateAddressshippingAddress;// 支付信息privatePaymentIdpaymentId;privateInstantpaidAt;// 发货信息privateShippingIdshippingId;privateInstantshippedAt;// 收货信息privateInstantdeliveredAt;// 取消信息privateStringcancelReason;privateInstantcancelledAt;// ========== 工厂方法 ==========/** * 创建订单 */publicstaticOrdercreate(CustomerIdcustomerId,List<OrderItem>items,AddressshippingAddress){// 业务规则验证if(items==null||items.isEmpty()){thrownewIllegalArgumentException("订单项不能为空");}if(shippingAddress==null){thrownewIllegalArgumentException("收货地址不能为空");}// 创建订单Orderorder=newOrder();order.id=OrderId.generate();order.customerId=customerId;order.items=newArrayList<>(items);// 防御性复制order.shippingAddress=shippingAddress;order.status=OrderStatus.CREATED;order.totalAmount=calculateTotalAmount(items);order.createdAt=Instant.now();// 发布领域事件order.registerEvent(newOrderCreatedEvent(order.id,order.customerId,order.items,order.totalAmount,order.shippingAddress,order.createdAt));returnorder;}privatestaticMoneycalculateTotalAmount(List<OrderItem>items){returnitems.stream().map(OrderItem::getSubtotal).reduce(Money.ZERO,Money::add);}// ========== 业务方法 ==========/** * 支付订单 */publicvoidpay(PaymentIdpaymentId,InstantpaidAt){// 状态验证if(status!=OrderStatus.CREATED){thrownewInvalidOrderStateException(String.format("只有待支付状态的订单才能支付,当前状态:%s",status));}// 状态变更this.status=OrderStatus.PAID;this.paymentId=paymentId;this.paidAt=paidAt;// 发布事件registerEvent(newOrderPaidEvent(this.id,paymentId,paidAt));}/** * 发货 */publicvoidship(ShippingIdshippingId,InstantshippedAt){if(status!=OrderStatus.PAID){thrownewInvalidOrderStateException(String.format("只有已支付状态的订单才能发货,当前状态:%s",status));}this.status=OrderStatus.SHIPPED;this.shippingId=shippingId;this.shippedAt=shippedAt;registerEvent(newOrderShippedEvent(this.id,shippingId,shippedAt));}/** * 确认收货 */publicvoidconfirmDelivery(InstantdeliveredAt){if(status!=OrderStatus.SHIPPED){thrownewInvalidOrderStateException(String.format("只有已发货状态的订单才能确认收货,当前状态:%s",status));}this.status=OrderStatus.DELIVERED;this.deliveredAt=deliveredAt;registerEvent(newOrderDeliveredEvent(this.id,deliveredAt));}/** * 取消订单 */publicvoidcancel(Stringreason,InstantcancelledAt){if(status!=OrderStatus.CREATED&&status!=OrderStatus.PAID){thrownewInvalidOrderStateException(String.format("只有待支付或已支付状态的订单才能取消,当前状态:%s",status));}OrderStatuspreviousStatus=this.status;this.status=OrderStatus.CANCELLED;this.cancelReason=reason;this.cancelledAt=cancelledAt;registerEvent(newOrderCancelledEvent(this.id,reason,previousStatus,cancelledAt));}/** * 添加订单项 */publicvoidaddItem(OrderItemitem){if(status!=OrderStatus.CREATED){thrownewInvalidOrderStateException("只有待支付状态的订单才能添加商品");}// 检查是否已存在相同商品Optional<OrderItem>existing=items.stream().filter(i->i.getProductId().equals(item.getProductId())).findFirst();if(existing.isPresent()){// 合并数量existing.get().increaseQuantity(item.getQuantity());}else{items.add(item);}// 重新计算总金额this.totalAmount=calculateTotalAmount(items);}/** * 移除订单项 */publicvoidremoveItem(OrderItemIditemId){if(status!=OrderStatus.CREATED){thrownewInvalidOrderStateException("只有待支付状态的订单才能移除商品");}booleanremoved=items.removeIf(item->item.getId().equals(itemId));if(!removed){thrownewIllegalArgumentException("订单项不存在:"+itemId);}if(items.isEmpty()){thrownewIllegalStateException("订单至少需要一个订单项");}// 重新计算总金额this.totalAmount=calculateTotalAmount(items);}// ========== Getters ==========publicOrderStatusgetStatus(){returnstatus;}publicCustomerIdgetCustomerId(){returncustomerId;}publicList<OrderItem>getItems(){returnCollections.unmodifiableList(items);}publicMoneygetTotalAmount(){returntotalAmount;}publicAddressgetShippingAddress(){returnshippingAddress;}publicPaymentIdgetPaymentId(){returnpaymentId;}// 不提供 Setters,状态只能通过业务方法修改}

3.3 订单项实体

// ✅ 订单项实体publicclassOrderItemextendsEntity<OrderItemId>{privateProductIdproductId;privateStringproductName;// 快照privateStringproductImage;// 快照privateMoneyunitPrice;// 快照privateintquantity;// 私有构造器,通过工厂方法创建privateOrderItem(){}publicstaticOrderItemcreate(ProductIdproductId,StringproductName,StringproductImage,MoneyunitPrice,intquantity){if(quantity<=0){thrownewIllegalArgumentException("数量必须大于0");}OrderItemitem=newOrderItem();item.id=OrderItemId.generate();item.productId=productId;item.productName=productName;item.productImage=productImage;item.unitPrice=unitPrice;item.quantity=quantity;returnitem;}publicMoneygetSubtotal(){returnunitPrice.multiply(quantity);}publicvoidincreaseQuantity(intdelta){if(delta<=0){thrownewIllegalArgumentException("增量必须大于0");}this.quantity+=delta;}publicvoiddecreaseQuantity(intdelta){if(delta<=0||delta>=this.quantity){thrownewIllegalArgumentException("减量无效");}this.quantity-=delta;}// Getters...}

3.4 应用服务实现

// ✅ 订单应用服务@Service@TransactionalpublicclassOrderApplicationService{@AutowiredprivateOrderRepositoryorderRepository;@AutowiredprivateProductGatewayproductGateway;@AutowiredprivateDomainEventPublishereventPublisher;/** * 创建订单 */publicOrderIdcreateOrder(CreateOrderCommandcommand){// 1. 构建订单项List<OrderItem>items=newArrayList<>();for(CreateOrderCommand.ItemDtoitemDto:command.getItems()){// 查询商品信息(通过网关)ProductInfoproduct=productGateway.getProduct(itemDto.getProductId());OrderItemitem=OrderItem.create(ProductId.of(itemDto.getProductId()),product.getName(),product.getImage(),Money.of(product.getPrice()),itemDto.getQuantity());items.add(item);}// 2. 构建收货地址AddressshippingAddress=newAddress(command.getAddress().getProvince(),command.getAddress().getCity(),command.getAddress().getDistrict(),command.getAddress().getDetail(),command.getAddress().getPostalCode(),command.getAddress().getReceiver(),command.getAddress().getPhone());// 3. 创建订单(领域逻辑)Orderorder=Order.create(CustomerId.of(command
http://www.jsqmd.com/news/1075357/

相关文章:

  • HS2-HF Patch:5分钟构建Honey Select 2专业级模组生态系统技术指南
  • Claude / Cursor 接入 API 常见报错与完整解决方案(新手避坑)
  • 新都桂湖入园避坑指南|公办摇号失败,社区优质蒙氏民办园完整择校清单
  • 基于Eclipse的CodeWarrior V10.x嵌入式开发环境深度解析与实践指南
  • 路径遍历漏洞深度解析:从原理到实战修复
  • 【2026年华为暑期实习-非AI方向(通软嵌软测试算法数据科学)- 6月24日-第三题- 容器镜像Top-K大小统计】(题目+思路+JavaC++Python解析+在线测试)
  • 英国邮编级医疗可及性分析管道:量化健康空间不平等
  • “伪”字系列的认知异化:论证伪主义在AI时代的意识形态扭曲与科学精神的系统性溃败
  • 泛基因组 | 分享一套“数据下载、质控、组装、矫正、注释到泛基因组统计与绘图“的泛基因组分析组装代码
  • MC9S08SE8中断与看门狗实战:从寄存器配置到系统稳定设计
  • 【共创季稿事节】鸿蒙原生 ArkTS 布局实战:Swiper + displayCount 多卡片轮播
  • 大模型API接入前的5道必答题:计费、认证、并发、审计、安全
  • 3分钟掌握手机号查QQ号:Python工具终极解决方案
  • Windows系统文件d3dx9_35.dll丢失找不到问题解决
  • 基于wechatbot云端提供的saas服务平台,自助开发微信机器人,仅需一句话!
  • 如何快速部署ChatLaw:完整的开源中文法律AI助手搭建指南
  • 终端检测与响应系统(EDR):构建主动、智能的终端安全防御体系 (售前模板)
  • 3个步骤搭建你的专属游戏串流服务器:Sunshine完全指南
  • 渔人的直感:FF14钓鱼计时器的完整使用指南
  • 3万款游戏上架、1000家厂商接入,鸿蒙游戏生态最新进展
  • 向量检索退化危机
  • 原神脚本:如何用3个功能解放90%的游戏时间?
  • 涉密机房外包运维 如何守住安全底线
  • Meta 发布三款自有品牌智能眼镜,更便宜好用,能否占领墨镜品类?
  • MySQL多表JOIN聚合磁盘溢出?分批聚合实战:某教育平台50万行数据从崩溃到稳定
  • 2026情感解惑APP实测对比:塔罗星盘、婚恋咨询怎么选?5款主流平台深度测评
  • 免费开源AMD Ryzen处理器调试工具SMUDebugTool终极指南
  • minimind系统学习教程 - 基础组件02:位置编码(Position encoding)
  • 【Agent Harness】Gliding Horse 的Token经济学:用 IRI 指针替代文本,让 Token 花在刀刃上
  • 工程师视角的AI技术简报:如何将Newsletter转化为可执行知识