贫血模型
奖池和奖项
class AwardPool {
int awardPoolId;
List<Award> awards;
public List<Award> getAwards() {
return awards;
}
public void setAwards(List<Award> awards) {
this.awards = awards;
}
......
}
class Award {
int awardId;
int probability;//概率
......
}
设计一个LotteryService,在其中的drawLottery()方法写服务逻辑
//sql查询,将数据映射到AwardPool对象
AwardPool awardPool = awardPoolDao.getAwardPool(poolId);
for (Award award : awardPool.getAwards()) {
//寻找到符合award.getProbability()概率的award
}
DDD工程实现
1. 限界上下文(即模块)
一般尽量用一个模块来表示一个领域的限界上下文
import com.company.team.bussiness.lottery.*;//抽奖上下文
import com.company.team.bussiness.riskcontrol.*;//风控上下文
import com.company.team.bussiness.counter.*;//计数上下文
import com.company.team.bussiness.condition.*;//活动准入上下文
import com.company.team.bussiness.stock.*;//库存上下文
模块内的组织结构
对于模块内的组织结构,一般情况下我们是按照领域对象、领域服务、领域资源库、防腐层等组织方式定义的。
import com.company.team.bussiness.lottery.domain.valobj.*;//领域对象-值对象
import com.company.team.bussiness.lottery.domain.entity.*;//领域对象-实体
import com.company.team.bussiness.lottery.domain.aggregate.*;//领域对象-聚合根
import com.company.team.bussiness.lottery.service.*;//领域服务
import com.company.team.bussiness.lottery.repo.*;//领域资源库
import com.company.team.bussiness.lottery.facade.*;//领域防腐层
2. 领域对象
抽奖(DrawLottery)聚合根和奖池(AwardPool)值对象
抽奖聚合根:抽奖活动的id、该活动下的所有可用奖池列表,
它的一个最主要的领域功能就是根据一个抽奖发生场景(DrawLotteryContext),选择出一个适配的奖池,即chooseAwardPool方法。
chooseAwardPool的逻辑:DrawLotteryContext会带有用户抽奖时的场景信息(抽奖得分或抽奖时所在的城市),DrawLottery会根据这个场景信息,匹配一个可以给用户发奖的AwardPool。
package com.company.team.bussiness.lottery.domain.aggregate;
import ...;
public class DrawLottery {
private int lotteryId; //抽奖id
private List<AwardPool> awardPools; //奖池列表
//getter & setter
public void setLotteryId(int lotteryId) {
if(id<=0){
throw new IllegalArgumentException("非法的抽奖id");
}
this.lotteryId = lotteryId;
}
//根据抽奖入参context选择奖池
public AwardPool chooseAwardPool(DrawLotteryContext context) {
if(context.getMtCityInfo()!=null) {
return chooseAwardPoolByCityInfo(awardPools, context.getMtCityInfo());
} else {
return chooseAwardPoolByScore(awardPools, context.getGameScore());
}
}
//根据抽奖所在城市选择奖池
private AwardPool chooseAwardPoolByCityInfo(List<AwardPool> awardPools, MtCifyInfo cityInfo) {
for(AwardPool awardPool: awardPools) {
if(awardPool.matchedCity(cityInfo.getCityId())) {
return awardPool;
}
}
return null;
}
//根据抽奖活动得分选择奖池
private AwardPool chooseAwardPoolByScore(List<AwardPool> awardPools, int gameScore) {...}
}
在匹配到一个具体的奖池之后,需要确定最后给用户的奖品是什么。这部分的领域功能在AwardPool内。
package com.company.team.bussiness.lottery.domain.valobj;
import ...;
public class AwardPool {
private String cityIds;//奖池支持的城市
private String scores;//奖池支持的得分
private int userGroupType;//奖池匹配的用户类型
private List<Awrad> awards;//奖池中包含的奖品
//当前奖池是否与城市匹配
public boolean matchedCity(int cityId) {...}
//当前奖池是否与用户得分匹配
public boolean matchedScore(int score) {...}
//根据概率选择奖池
public Award randomGetAward() {
int sumOfProbablity = 0;
for(Award award: awards) {
sumOfProbability += award.getAwardProbablity();
}
int randomNumber = ThreadLocalRandom.current().nextInt(sumOfProbablity);
range = 0;
for(Award award: awards) {
range += award.getProbablity();
if(randomNumber<range) {
return award;
}
}
return null;
}
}
与以往的仅有getter、setter的业务对象不同,领域对象具有了行为,对象更加丰满。同时,比起将这些逻辑写在服务内(例如**Service),领域功能的内聚性更强,职责更加明确。
3. 资源库
Repository,资源库对外的整体访问由Repository提供
数据库,分布式缓存,本地缓存
//数据库资源
import com.company.team.bussiness.lottery.repo.dao.AwardPoolDao;//数据库访问对象-奖池
import com.company.team.bussiness.lottery.repo.dao.AwardDao;//数据库访问对象-奖品
import com.company.team.bussiness.lottery.repo.dao.po.AwardPO;//数据库持久化对象-奖品
import com.company.team.bussiness.lottery.repo.dao.po.AwardPoolPO;//数据库持久化对象-奖池
import com.company.team.bussiness.lottery.repo.cache.DrawLotteryCacheAccessObj;//分布式缓存访问对象-抽奖缓存访问
import com.company.team.bussiness.lottery.repo.repository.DrawLotteryRepository;//资源库访问对象-抽奖资源库
package com.company.team.bussiness.lottery.repo;
import ...;
@Repository
public class DrawLotteryRepository {
@Autowired
private AwardDao awardDao;
@Autowired
private AwardPoolDao awardPoolDao;
@AutoWired
private DrawLotteryCacheAccessObj drawLotteryCacheAccessObj;
public DrawLottery getDrawLotteryById(int lotteryId) {
DrawLottery drawLottery = drawLotteryCacheAccessObj.get(lotteryId);
if(drawLottery!=null){
return drawLottery;
}
drawLottery = getDrawLotteyFromDB(lotteryId);
drawLotteryCacheAccessObj.add(lotteryId, drawLottery);
return drawLottery;
}
private DrawLottery getDrawLotteryFromDB(int lotteryId) {...}
}
4. 防腐层
在一个上下文中,有时需要对外部上下文进行访问。
package com.company.team.bussiness.lottery.facade;
import ...;
@Component
public class UserCityInfoFacade {
@Autowired
private LbsService lbsService;//外部用户城市信息RPC服务
public MtCityInfo getMtCityInfo(LotteryContext context) {
LbsReq lbsReq = new LbsReq();
lbsReq.setLat(context.getLat());
lbsReq.setLng(context.getLng());
LbsResponse resp = lbsService.getLbsCityInfo(lbsReq);
return buildMtCifyInfo(resp);
}
private MtCityInfo buildMtCityInfo(LbsResponse resp) {...}
}
5. 领域服务
package com.company.team.bussiness.lottery.service.impl
import ...;
@Service
public class LotteryServiceImpl implements LotteryService {
@Autowired
private DrawLotteryRepository drawLotteryRepo;
@Autowired
private UserCityInfoFacade UserCityInfoFacade;
@Autowired
private AwardSendService awardSendService;
@Autowired
private AwardCounterFacade awardCounterFacade;
@Override
public IssueResponse issueLottery(LotteryContext lotteryContext) {
DrawLottery drawLottery = drawLotteryRepo.getDrawLotteryById(lotteryContext.getLotteryId());//获取抽奖配置聚合根
awardCounterFacade.incrTryCount(lotteryContext);//增加抽奖计数信息
AwardPool awardPool = lotteryConfig.chooseAwardPool(bulidDrawLotteryContext(drawLottery, lotteryContext));//选中奖池
Award award = awardPool.randomChooseAward();//选中奖品
return buildIssueResponse(awardSendService.sendAward(award, lotteryContext));//发出奖品实体
}
private IssueResponse buildIssueResponse(AwardSendResponse awardSendResponse) {...}
}
package ...;
import ...;
@Service
public class LotteryApplicationService {
@Autowired
private LotteryRiskService riskService;
@Autowired
private LotteryConditionService conditionService;
@Autowired
private LotteryService lotteryService;
//用户参与抽奖活动
public Response<PrizeInfo, ErrorData> participateLottery(LotteryContext lotteryContext) {
//校验用户登录信息
validateLoginInfo(lotteryContext);
//校验风控
RiskAccessToken riskToken = riskService.accquire(buildRiskReq(lotteryContext));
...
//活动准入检查
LotteryConditionResult conditionResult = conditionService.checkLotteryCondition(otteryContext.getLotteryId(),lotteryContext.getUserId());
...
//抽奖并返回结果
IssueResponse issueResponse = lotteryService.issurLottery(lotteryContext);
if(issueResponse!=null && issueResponse.getCode()==IssueResponse.OK) {
return buildSuccessResponse(issueResponse.getPrizeInfo());
} else {
return buildErrorResponse(ResponseCode.ISSUE_LOTTERY_FAIL, ResponseMsg.ISSUE_LOTTERY_FAIL)
}
}
private void validateLoginInfo(LotteryContext lotteryContext){...}
private Response<PrizeInfo, ErrorData> buildErrorResponse (int code, String msg){...}
private Response<PrizeInfo, ErrorData> buildSuccessResponse (PrizeInfo prizeInfo){...}
}