Linux系统架构解析与高效运维实践指南
2026/7/17 22:10:07
聚合是DDD中的业务一致性边界,将相关的实体和值对象组合成一个整体,确保聚合内的数据始终保持一致性。
核心原则:
聚合根是聚合的唯一入口,负责:
| 特征 | 实体 | 值对象 |
|---|---|---|
| 唯一标识 | 有(如OrderID) | 无(通过属性值唯一) |
| 可变性 | 可变(如订单状态变更) | 不可变(如地址修改需创建新对象) |
| 生命周期 | 独立,由聚合根管理 | 依赖于实体,无独立生命周期 |
| 相等性 | 通过ID判断 | 通过属性值判断 |
业务场景:用户下单流程
订单聚合(Order Aggregate):
用户聚合(User Aggregate):
商品聚合(Product Aggregate):
@AggregateRootpublicclassOrder{// 聚合根唯一标识privateOrderIDorderId;// 关联外部聚合的ID(非直接引用)privateUserIDuserId;// 聚合内实体集合privateList<OrderItem>orderItems;// 聚合内值对象privateAddressshippingAddress;privatePaymentInfopaymentInfo;// 聚合根状态privateOrderStatusstatus;privateBigDecimaltotalAmount;privateLocalDateTimecreatedAt;privateLocalDateTimeupdatedAt;// 构造函数和业务方法...}// 构造函数:创建订单,确保订单项数量和金额一致publicOrder(OrderIDorderId,UserIDuserId,List<OrderItem>orderItems,AddressshippingAddress){// 业务规则1:订单项不能为空if(CollectionUtils.isEmpty(orderItems)){thrownewDomainException("订单项不能为空");}// 业务规则2:计算总金额,确保与订单项金额一致BigDecimalcalculatedTotal=orderItems.stream().map(OrderItem::getTotalPrice).reduce(BigDecimal.ZERO,BigDecimal::add);// 初始化聚合根this.orderId=orderId;this.userId=userId;this.orderItems=newArrayList<>(orderItems);this.shippingAddress=shippingAddress;this.totalAmount=calculatedTotal;this.status=OrderStatus.CREATED;this.createdAt=LocalDateTime.now();this.updatedAt=LocalDateTime.now();}// 取消订单:更新订单状态,确保所有订单项状态一致publicvoidcancel(){// 业务规则:只有待支付状态的订单才能取消if(this.status!=OrderStatus.CREATED){thrownewDomainException("只有待支付状态的订单才能取消");}// 更新订单状态this.status=OrderStatus.CANCELLED;this.updatedAt=LocalDateTime.now();// 无需更新订单项状态,因为订单项状态由订单状态驱动}// 添加订单项:确保金额一致性publicvoidaddOrderItem(OrderItemorderItem){// 业务规则:订单已支付或取消,不能添加订单项if(this.status!=OrderStatus.CREATED){thrownewDomainException("订单已支付或取消,不能添加订单项");}// 添加订单项this.orderItems.add(orderItem);// 更新总金额this.totalAmount=this.totalAmount.add(orderItem.getTotalPrice());this.updatedAt=LocalDateTime.now();}publicclassOrderItem{// 订单项唯一标识(在聚合内唯一,全局唯一需包含OrderID)privateOrderItemIDorderItemId;// 商品快照(值对象,记录下单时的商品信息)privateProductSnapshotproductSnapshot;// 订单项属性privateIntegerquantity;privateBigDecimalunitPrice;privateBigDecimaltotalPrice;// 构造函数和业务方法...}// 构造函数:确保订单项金额计算正确publicOrderItem(OrderItemIDorderItemId,ProductSnapshotproductSnapshot,Integerquantity){// 业务规则1:数量必须大于0if(quantity<=0){thrownewDomainException("订单项数量必须大于0");}// 业务规则2:单价必须大于0if(productSnapshot.getPrice().compareTo(BigDecimal.ZERO)<=0){thrownewDomainException("商品单价必须大于0");}this.orderItemId=orderItemId;this.productSnapshot=productSnapshot;this.quantity=quantity;this.unitPrice=productSnapshot.getPrice();this.totalPrice=this.unitPrice.multiply(newBigDecimal(quantity));}// 更新数量:确保金额同步更新publicvoidupdateQuantity(IntegernewQuantity){// 业务规则:数量必须大于0if(newQuantity<=0){thrownewDomainException("订单项数量必须大于0");}this.quantity=newQuantity;this.totalPrice=this.unitPrice.multiply(newBigDecimal(newQuantity));}// 值对象:不可变,通过属性值判断相等性publicclassAddress{privatefinalStringprovince;privatefinalStringcity;privatefinalStringdistrict;privatefinalStringdetail;privatefinalStringzipCode;privatefinalStringcontactName;privatefinalStringcontactPhone;// 构造函数:一次性初始化,无setter方法publicAddress(Stringprovince,Stringcity,Stringdistrict,Stringdetail,StringzipCode,StringcontactName,StringcontactPhone){// 业务规则验证if(StringUtils.isBlank(province)){thrownewDomainException("省份不能为空");}// 其他规则验证...this.province=province;this.city=city;this.district=district;this.detail=detail;this.zipCode=zipCode;this.contactName=contactName;this.contactPhone=contactPhone;}// 只提供getter方法,无setterpublicStringgetProvince(){returnprovince;}// 其他getter方法...// 值对象相等性判断:通过所有属性值比较@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Addressaddress=(Address)o;returnObjects.equals(province,address.province)&&Objects.equals(city,address.city)&&Objects.equals(district,address.district)&&Objects.equals(detail,address.detail)&&Objects.equals(zipCode,address.zipCode)&&Objects.equals(contactName,address.contactName)&&Objects.equals(contactPhone,address.contactPhone);}}findById方法必须返回完整的聚合根实例,包含所有关联的实体和值对象// 只针对聚合根Order的仓储publicinterfaceOrderRepository{// 保存完整聚合voidsave(Orderorder);// 根据聚合根ID加载完整聚合Optional<Order>findById(OrderIDorderId);// 删除完整聚合voiddelete(Orderorder);// 根据业务条件查询聚合根列表List<Order>findByUserId(UserIDuserId);List<Order>findByStatus(OrderStatusstatus);}// 1. 准备订单数据UserIDuserId=newUserID("user-123");OrderIDorderId=newOrderID("order-456");// 2. 创建值对象AddressshippingAddress=newAddress("广东省","深圳市","南山区","科技园","518000","张三","13800138000");ProductSnapshotproductSnapshot1=newProductSnapshot(newProductID("product-789"),"iPhone 15",newBigDecimal(9999),newBigDecimal(9999));ProductSnapshotproductSnapshot2=newProductSnapshot(newProductID("product-012"),"AirPods Pro",newBigDecimal(1999),newBigDecimal(1999));// 3. 创建实体(通过聚合根方法,而非直接实例化)OrderItemorderItem1=newOrderItem(newOrderItemID("order-item-345"),productSnapshot1,1);OrderItemorderItem2=newOrderItem(newOrderItemID("order-item-678"),productSnapshot2,2);// 4. 创建聚合根(确保聚合内一致性)Orderorder=newOrder(orderId,userId,Arrays.asList(orderItem1,orderItem2),shippingAddress);// 5. 调用聚合根业务方法order.addOrderItem(newOrderItem(newOrderItemID("order-item-901"),productSnapshot1,1));// 6. 保存完整聚合到仓储orderRepository.save(order);// 1. 从仓储加载完整聚合Optional<Order>orderOpt=orderRepository.findById(newOrderID("order-456"));if(orderOpt.isPresent()){Orderorder=orderOpt.get();// 2. 调用聚合根业务方法(确保业务规则)order.cancel();// 3. 保存更新后的聚合orderRepository.save(order);}问题:将用户、订单、商品都放在同一个聚合内,导致聚合过大,性能下降
解决:按业务边界拆分,用户、订单、商品分别作为独立聚合,通过ID关联
问题:订单聚合直接引用User对象,导致订单聚合依赖用户聚合的所有变化
解决:订单聚合只保存UserID,需要用户信息时通过UserID查询User聚合
问题:外部系统直接修改OrderItem的数量,绕过了Order聚合根的业务规则
解决:将OrderItem的setter方法设为私有,只能通过Order的addOrderItem或updateOrderItem方法修改
问题:一个订单包含数百个订单项,导致聚合加载和保存性能下降
解决:考虑将订单项拆分为独立聚合,或使用分页加载
| 聚合根 | 聚合内实体 | 聚合内值对象 | 核心业务规则 |
|---|---|---|---|
| Order | OrderItem | Address、PaymentInfo、ProductSnapshot | 1. 订单项不能为空 2. 订单总金额等于订单项金额之和 3. 只有待支付状态的订单才能取消 4. 订单项数量必须大于0 |
| User | - | UserProfile、ContactInfo | 1. 用户名不能为空 2. 手机号必须唯一 |
| Product | - | ProductDetail、PriceInfo | 1. 商品名称不能为空 2. 商品价格必须大于0 |
通过合理设计聚合根和聚合对象,可以构建出清晰、一致、高性能的DDD领域模型,为微服务架构奠定坚实的基础。