基于领域事件驱动架构实现霸王餐申请、审核、核销的松耦合流程

基于领域事件驱动架构实现霸王餐申请、审核、核销的松耦合流程

在“外卖霸王餐”系统中,试吃订单从用户申请、运营审核到商户核销涉及多个子系统(用户中心、风控、通知、积分、财务)。若采用同步调用链,将导致模块紧耦合、故障传播快、扩展困难。本文基于baodanbao.com.cn.*包结构,使用Spring ApplicationEvent + 异步监听器实现领域事件驱动架构,确保核心流程高内聚、跨域协作低耦合。

1. 定义领域事件

每个关键业务动作发布一个事件:

package baodanbao.com.cn.event;

import org.springframework.context.ApplicationEvent;

public abstract class TrialOrderEvent extends ApplicationEvent {
    private final String orderId;
    private final Long userId;

    public TrialOrderEvent(Object source, String orderId, Long userId) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
    }

    public String getOrderId() { return orderId; }
    public Long getUserId() { return userId; }
}

// 用户提交申请
public class TrialOrderAppliedEvent extends TrialOrderEvent {
    public TrialOrderAppliedEvent(Object source, String orderId, Long userId) {
        super(source, orderId, userId);
    }
}

// 运营审核通过
public class TrialOrderApprovedEvent extends TrialOrderEvent {
    public TrialOrderApprovedEvent(Object source, String orderId, Long userId) {
        super(source, orderId, userId);
    }
}

// 商户完成核销
public class TrialOrderRedeemedEvent extends TrialOrderEvent {
    private final String merchantId;

    public TrialOrderRedeemedEvent(Object source, String orderId, Long userId, String merchantId) {
        super(source, orderId, userId);
        this.merchantId = merchantId;
    }

    public String getMerchantId() { return merchantId; }
}

在这里插入图片描述

2. 事件发布:在领域服务中触发

package baodanbao.com.cn.service;

import baodanbao.com.cn.event.TrialOrderAppliedEvent;
import baodanbao.com.cn.event.TrialOrderApprovedEvent;
import baodanbao.com.cn.event.TrialOrderRedeemedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TrialOrderDomainService {

    private final ApplicationEventPublisher eventPublisher;
    private final TrialOrderRepository orderRepository;

    public TrialOrderDomainService(ApplicationEventPublisher eventPublisher,
                                   TrialOrderRepository orderRepository) {
        this.eventPublisher = eventPublisher;
        this.orderRepository = orderRepository;
    }

    @Transactional
    public void applyTrialOrder(String orderId, Long userId) {
        // 1. 保存订单为 PENDING
        orderRepository.saveNew(orderId, userId, "PENDING");
        // 2. 发布领域事件(事务提交后发送)
        eventPublisher.publishEvent(new TrialOrderAppliedEvent(this, orderId, userId));
    }

    @Transactional
    public void approveOrder(String orderId, Long userId) {
        orderRepository.updateStatus(orderId, "APPROVED");
        eventPublisher.publishEvent(new TrialOrderApprovedEvent(this, orderId, userId));
    }

    @Transactional
    public void redeemOrder(String orderId, Long userId, String merchantId) {
        orderRepository.updateStatus(orderId, "REDEEMED");
        eventPublisher.publishEvent(new TrialOrderRedeemedEvent(this, orderId, userId, merchantId));
    }
}

3. 异步事件监听器(解耦处理)

风控检查(申请后)

package baodanbao.com.cn.listener;

import baodanbao.com.cn.event.TrialOrderAppliedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class RiskControlListener {

    @Async
    @EventListener
    public void handleApplied(TrialOrderAppliedEvent event) {
        // 调用风控服务:是否频繁申请、黑名单等
        boolean risky = riskService.check(event.getUserId());
        if (risky) {
            // 自动拒绝或人工复审
            trialOrderService.reject(event.getOrderId());
        }
    }
}

消息通知(审核通过后)

@Component
public class NotificationListener {

    @Async
    @EventListener
    public void onApproved(TrialOrderApprovedEvent event) {
        // 发送站内信 + 短信
        messageService.send(event.getUserId(), "您的霸王餐申请已通过,请及时确认!");
    }
}

积分发放(核销完成后)

@Component
public class RewardListener {

    @Async
    @EventListener
    public void onRedeemed(TrialOrderRedeemedEvent event) {
        // 发放积分/优惠券
        rewardService.grantPoints(event.getUserId(), 50);
        // 记录商户营销成本
        financeService.recordCost(event.getMerchantId(), event.getOrderId(), 30.0);
    }
}

4. 启用异步与事务边界控制

配置异步线程池并确保事件在事务提交后发布:

package baodanbao.com.cn.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.event.TransactionPhase;

// 注意:上述监听器实际应使用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
// 但为简化示例,此处用 @EventListener + @Async,需保证 publisher 在事务外调用
// 更严谨做法:使用 ApplicationEventMulticaster 自定义 executor

@Configuration
@EnableAsync
@EnableTransactionManagement
public class EventConfig {
}

实际生产建议使用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 替代 @EventListener,避免事务回滚时事件已消费。

5. 事件持久化与重试(增强可靠性)

为防止监听器失败导致数据不一致,可引入事件表:

@Entity
@Table(name = "domain_event")
public class StoredEvent {
    @Id
    private String id;
    private String eventType;
    private String payloadJson; // JSON序列化事件内容
    private boolean processed = false;
    private LocalDateTime createdAt;
}

监听器处理成功后标记processed=true,后台任务定期重试未处理事件。

6. 控制器调用示例

@RestController
@RequestMapping("/api/trial")
public class TrialOrderController {

    private final TrialOrderDomainService domainService;

    @PostMapping("/apply")
    public ResponseEntity<Void> apply(@RequestBody ApplyRequest req) {
        String orderId = IdGenerator.next();
        domainService.applyTrialOrder(orderId, req.getUserId());
        return ResponseEntity.accepted().build();
    }

    @PostMapping("/{orderId}/approve")
    public void approve(@PathVariable String orderId) {
        TrialOrder order = orderRepo.findById(orderId);
        domainService.approveOrder(orderId, order.getUserId());
    }
}

通过领域事件,各子系统仅依赖事件契约,无需知晓彼此存在。新增“邀请好友得奖励”功能时,只需增加一个监听TrialOrderRedeemedEvent的组件,主流程零修改。

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值