应用场景
日常开发中,遇到这样一个场景:做一个用户会员权益发放模块,权益的类型可能有:平台积分、礼包、优惠券、支付卷、红包、兑换码、爱奇艺会员兑换码等;这些权益来自不同的部门内外的系统,甚至是跨公司合作。当然可以为每种不同的权益单独设计对应的模块提供服务,但是更合适的做法是:抽象出权益模型,使用权益类型区分,这样权益可以统一存储,打包权益的发放可以由DN事务天然的支持,并且可通过用户表示UID对用户权益分表;
此外,发放权益和查询用户权益这2个功能是每个权益类型服务都应该具备的,因此可以抽象成权益服务模板,让各个权益类型实现这个模板;当需要对接新权益或移除旧权益时,只需要调整实现类和权益类型枚举,而不需要修改主流程的代码逻辑。
架构设计
该设计的核心在于
- 策略模式,实现了每一个特定权益的特性
- 模板方法,把每一个权益的公共方法抽取出来。
代码示例
/**
* 权益工厂
*/
@Service
public class BenefitServiceFactory {
@Autowired
private List<BenefitBaseTemplate> list;
private static Map<Integer, BenefitBaseTemplate> typeServiceMap = Maps.newConcurrentMap();
@PostConstruct
public void init() {
map = list.stream().collect(Collectors.toMap(BenefitBaseTemplate::getApprovalType, obj -> obj));
}
/**
* 根据权益类型获取对应服务
* @param benefitType
* @return
*/
public BenefitBaseTemplate getService(Integer benefitType) {
return typeServiceMap.get(benefitType);
}
}
策略执行,根据权益类型获取对应的权益发放服务,执行权益发送
// 取出service
BenefitBaseTemplate benefitService = benefitServiceFactory.getService(receiveBenefitReqDTO.getBenefitType());
// 同步
benefitService.syncSendBenefit(receiveBenefitReqDTO);
模板方法,所有的权益都是构造请求,入库,调用RPC发送请求。但是sendBenefit
由子类实现。
@Slf4j
public abstract class BenefitBaseTemplate {
/**
* 同步发放权益
*/
public ReceiveBenefitResp syncSendBenefit(ReceiveBenefitReqDTO receiveBenefitReqDTO){
// 构造请求参数
SendBenefitReq sendBenefitReq = buildSendBenefitReq(receiveBenefitReqDTO);
// 请求数据先入库,失败直接返回
reqDataIntoDB(sendBenefitReq);
SendBenefitResp sendResult;
try {
// 入库成功再调用RPC发放权益
sendResult = this.sendBenefit(sendBenefitReq);
// 更新用户权益信息
updateUserBenefitAfterSendSuc(sendResult);
} catch (Exception e) {
log.error("[benefit receive]syncSendBenefitByType_error_rollback! [receiveBenefitReqDTO={}]", JSON.toJSONString(receiveBenefitReqDTO));
deleteReqDataInDB(sendBenefitReq);
throw e;
}
return new ReceiveBenefitResp(sendResult);
}
/**
* 发放权益
*
* @param sendBenefitReq
* @return
*/
public abstract SendBenefitResp sendBenefit(SendBenefitReq sendBenefitReq);
}
礼券服务
/**
* 礼券服务
*/
@Slf4j
@Service("ticketService")
public class TicketServiceImpl extends BenefitBaseTemplate implements TicketService {
@HystrixCommand(groupKey = "TicketServiceGroupKey", commandKey = "sendTicketCommandKey", fallbackMethod = "sendTicketFallback", ignoreExceptions = BusinessException.class)
@Override
public SendBenefitResp sendBenefit(SendBenefitReq sendTicketReq) {
/*...*/
ResponseInfo<List<SaveTicketVO>> facadeResult;
try {
facadeResult = platformUserTicketFacade.sendUserTickets(param);
} catch (Throwable e) {
log.error("[SERIOUS_DUBBO]UserTicketFacade.sendUserTicket_error! [openid={} ticketId={} sendNo={}] e:{}",
sendTicketReq.getOpenid(), sendTicketReq.getBenefitId(), sendTicketReq.getSendNo(), e);
throw new RuntimeException();
}
}
}