基于DDD领域驱动设计的淘客返利系统核心模型构建实践
又见面了,我是高佣返利省赚客APP研发者微赚!
在复杂的电商返利业务中,传统的贫血模型往往导致业务逻辑散落在Service层,代码难以维护且极易产生Bug。为了解决这一痛点,省赚客APP的核心重构引入了领域驱动设计(DDD),通过限界上下文划分、聚合根设计与领域事件驱动,构建了高内聚、低耦合的核心模型。
限界上下文的战略划分
首先,我们将庞大的单体系统拆分为多个限界上下文(Bounded Context)。在返利场景中,主要划分为“订单上下文”、“佣金上下文”和“用户资产上下文”。每个上下文拥有独立的领域模型和数据库Schema,通过防腐层(ACL)进行交互,避免模型污染。
package juwatech.cn.provinceearn.domain.order.context;
import juwatech.cn.provinceearn.domain.order.entity.RebateOrder;
import juwatech.cn.provinceearn.domain.order.valueobject.OrderStatus;
import juwatech.cn.provinceearn.domain.order.repository.OrderRepository;
import juwatech.cn.provinceearn.domain.commission.event.OrderSettledEvent;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 订单上下文中的聚合根服务
*/
@Component
public class OrderContextService {
private final OrderRepository orderRepository;
public OrderContextService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public void confirmOrderReceipt(String orderId) {
RebateOrder order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("Order not found"));
// 领域行为:确认收货,状态流转内部完成
order.confirmReceipt();
if (order.getStatus() == OrderStatus.SETTLED) {
// 发布领域事件,解耦佣金计算逻辑
order.registerEvent(new OrderSettledEvent(
order.getId(),
order.getUserId(),
order.getActualAmount(),
LocalDateTime.now()
));
orderRepository.save(order);
}
}
}
富血模型的聚合根设计
在“佣金上下文”中,我们摒弃了简单的Getter/Setter,将业务规则封装在聚合根内部。佣金计算涉及复杂的费率阶梯、活动叠加和风控规则,这些逻辑必须由CommissionAggregate自行守护不变性。
package juwatech.cn.provinceearn.domain.commission.aggregate;
import juwatech.cn.provinceearn.domain.commission.valueobject.CommissionRate;
import juwatech.cn.provinceearn.domain.commission.valueobject.Money;
import juwatech.cn.provinceearn.domain.user.entity.UserLevel;
import juwatech.cn.provinceearn.domain.common.exception.BusinessRuleException;
import java.math.BigDecimal;
/**
* 佣金聚合根
*/
public class CommissionAggregate {
private String id;
private String orderId;
private Money estimatedAmount;
private boolean isCalculated;
public CommissionAggregate(String orderId, Money orderAmount, UserLevel userLevel) {
this.orderId = orderId;
this.isCalculated = false;
// 领域逻辑:根据用户等级和订单金额动态计算预估佣金
this.calculateInternal(orderAmount, userLevel);
}
private void calculateInternal(Money orderAmount, UserLevel userLevel) {
// 封装复杂的风控与费率计算逻辑
if (orderAmount.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessRuleException("Invalid order amount");
}
CommissionRate rate = CommissionRateFactory.getRate(userLevel, orderAmount);
BigDecimal commission = orderAmount.getAmount().multiply(rate.getRate());
// 确保佣金不低于最小阈值
if (commission.compareTo(rate.getMinThreshold()) < 0) {
commission = rate.getMinThreshold();
}
this.estimatedAmount = new Money(commission);
this.isCalculated = true;
}
public Money getEstimatedAmount() {
if (!isCalculated) {
throw new IllegalStateException("Commission not calculated yet");
}
return this.estimatedAmount;
}
public void freeze() {
// 冻结佣金,防止重复计算
this.isCalculated = false;
}
}
领域事件驱动的最终一致性
跨上下文的协作不再依赖同步RPC调用,而是通过领域事件实现最终一致性。当订单状态变更时,发布OrderSettledEvent,佣金上下文监听该事件并触发佣金入账,用户资产上下文监听佣金入账事件更新余额。
package juwatech.cn.provinceearn.domain.commission.handler;
import juwatech.cn.provinceearn.domain.commission.aggregate.CommissionAggregate;
import juwatech.cn.provinceearn.domain.commission.repository.CommissionRepository;
import juwatech.cn.provinceearn.domain.order.event.OrderSettledEvent;
import juwatech.cn.provinceearn.domain.user.service.UserLevelService;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
public class CommissionEventHandler {
private final CommissionRepository commissionRepository;
private final UserLevelService userLevelService;
public CommissionEventHandler(CommissionRepository commissionRepository, UserLevelService userLevelService) {
this.commissionRepository = commissionRepository;
this.userLevelService = userLevelService;
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderSettled(OrderSettledEvent event) {
// 异步或同步处理取决于配置,此处演示核心逻辑
var userLevel = userLevelService.getCurrentLevel(event.getUserId());
// 构建新的佣金聚合
CommissionAggregate newCommission = new CommissionAggregate(
event.getOrderId(),
event.getOrderAmount(),
userLevel
);
// 持久化聚合
commissionRepository.save(newCommission);
// 此处可继续发布 CommissionCreatedEvent 触发后续流程
}
}
值对象的不可变性保障
在DDD中,值对象(Value Object)至关重要。我们定义了严格的Money和CommissionRate值对象,确保所有涉及金额的计算都是不可变的,避免了多线程环境下的数据竞争。
package juwatech.cn.provinceearn.domain.common.valueobject;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 金额值对象,保证不可变性和精度
*/
public final class Money {
private final BigDecimal amount;
public Money(BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount must be non-negative");
}
this.amount = amount.setScale(2, RoundingMode.HALF_UP);
}
public Money add(Money other) {
return new Money(this.amount.add(other.amount));
}
public BigDecimal getAmount() {
return amount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0;
}
@Override
public int hashCode() {
return amount.hashCode();
}
}
通过上述实践,省赚客APP成功将复杂的业务逻辑收敛于领域模型之中,代码可读性与可测试性显著提升。面对频繁变动的返利规则,我们只需修改相应的聚合根或策略类,无需牵一发而动全身。这种架构不仅支撑了当前的亿级流量,更为未来的业务扩展奠定了坚实基础。
本文著作权归 省赚客app 研发团队,转载请注明出处!

188

被折叠的 条评论
为什么被折叠?



