开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。
今日头条、抖音推荐算法原理全文详解
苏宁数据中台架构实践
支付宝 App 启动性能优化
支付宝 App架构的原理与实战
IDEA 2020,9个新特性曝光,程序员:追不上了...
点击“开发者技术前线”,选择“星标🔝”
在看|星标|留言, 真爱
文章来源:美团技术团队
原标题:领域驱动设计在美团点架评业务系统构
classAwardPool{
int awardPoolId;
List<Award> awards;
publicList<Award> getAwards() {
return awards;
}
publicvoid setAwards(List<Award> awards) {
this.awards = awards;
}
......
}
classAward{
int awardId;
int probability;//概率
......
}
AwardPool awardPool = awardPoolDao.getAwardPool(poolId);//sql查询,将数据映射到AwardPool对象
for(Award award : awardPool.getAwards()) {
//寻找到符合award.getProbability()概率的award
}
分治,把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题需要足够小,以便一个人单枪匹马就能够解决他们;其次,必须考虑如何将分割后的各个部分装配为整体。分割得越合理越易于理解,在装配成整体时,所需跟踪的细节也就越少。即更容易设计各部分的协作方式。评判什么是分治得好,即高内聚低耦合。
抽象,使用抽象能够精简问题空间,而且问题越小越容易理解。举个例子,从北京到上海出差,可以先理解为使用交通工具前往,但不需要一开始就想清楚到底是高铁还是飞机,以及乘坐他们需要注意什么。
知识,顾名思义,DDD可以认为是知识的一种。
业务架构——根据业务需求设计业务模块及其关系
系统架构——设计系统和子系统的模块
技术架构——决定采用的技术及框架
根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;
对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
为聚合根设计仓储,并思考实体或值对象的创建方式;
在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。
抽奖活动有活动限制,例如用户的抽奖次数限制,抽奖的开始和结束的时间等;
一个抽奖活动包含多个奖品,可以针对一个或多个用户群体;
奖品有自身的奖品配置,例如库存量,被抽中的概率等,最多被一个用户抽中的次数等等;
用户群体有多种区别方式,如按照用户所在城市区分,按照新老客区分等;
活动具有风控配置,能够限制用户参与抽奖的频率。
任务更好拆分,一个开发人员可以全身心的投入到相关的一个单独的上下文中;
沟通更加顺畅,一个上下文可以明确自己对其他上下文的依赖关系,从而使得团队内开发直接更好的对接。
每个团队在它的上下文中能够更加明确自己领域内的概念,因为上下文是领域的解系统;
对于限界上下文之间发生交互,团队与上下文的一致性,能够保证我们明确对接的团队和依赖的上下游。
合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。
开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。
发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。
大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
另谋他路(SeparateWay):两个完全没有任何联系的上下文。
边界内的内容具有一致性:在一个事务中只修改一个聚合实例。如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
设计小聚合:大部分的聚合都可以只包含根实体,而无需包含其他实体。即使一定要包含,可以考虑将其创建为值对象。
通过唯一标识来引用其他聚合或实体:当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象。如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象。如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。
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.*;//领域防腐层
package com.company.team.bussiness.lottery.domain.aggregate;
import...;
publicclassDrawLottery{
privateint lotteryId; //抽奖id
privateList<AwardPool> awardPools; //奖池列表
//getter & setter
publicvoid setLotteryId(int lotteryId) {
if(id<=0){
thrownewIllegalArgumentException("非法的抽奖id");
}
this.lotteryId = lotteryId;
}
//根据抽奖入参context选择奖池
publicAwardPool chooseAwardPool(DrawLotteryContext context) {
if(context.getMtCityInfo()!=null) {
return chooseAwardPoolByCityInfo(awardPools, context.getMtCityInfo());
} else{
return chooseAwardPoolByScore(awardPools, context.getGameScore());
}
}
//根据抽奖所在城市选择奖池
privateAwardPool chooseAwardPoolByCityInfo(List<AwardPool> awardPools, MtCifyInfo cityInfo) {
for(AwardPool awardPool: awardPools) {
if(awardPool.matchedCity(cityInfo.getCityId())) {
return awardPool;
}
}
returnnull;
}
//根据抽奖活动得分选择奖池
privateAwardPool chooseAwardPoolByScore(List<AwardPool> awardPools, int gameScore) {...}
}
package com.company.team.bussiness.lottery.domain.valobj;
import...;
publicclassAwardPool{
privateString cityIds;//奖池支持的城市
privateString scores;//奖池支持的得分
privateint userGroupType;//奖池匹配的用户类型
privateList<Awrad> awards;//奖池中包含的奖品
//当前奖池是否与城市匹配
publicboolean matchedCity(int cityId) {...}
//当前奖池是否与用户得分匹配
publicboolean matchedScore(int score) {...}
//根据概率选择奖池
publicAward 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;
}
}
returnnull;
}
}
//数据库资源
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
publicclassDrawLotteryRepository{
@Autowired
privateAwardDao awardDao;
@Autowired
privateAwardPoolDao awardPoolDao;
@AutoWired
privateDrawLotteryCacheAccessObj drawLotteryCacheAccessObj;
publicDrawLottery getDrawLotteryById(int lotteryId) {
DrawLottery drawLottery = drawLotteryCacheAccessObj.get(lotteryId);
if(drawLottery!=null){
return drawLottery;
}
drawLottery = getDrawLotteyFromDB(lotteryId);
drawLotteryCacheAccessObj.add(lotteryId, drawLottery);
return drawLottery;
}
privateDrawLottery getDrawLotteryFromDB(int lotteryId) {...}
}
需要将外部上下文中的模型翻译成本上下文理解的模型。
不同上下文之间的团队协作关系,如果是供奉者关系,建议引入防腐层,避免外部上下文变化对本上下文的侵蚀。
该访问本上下文使用广泛,为了避免改动影响范围过大。
package com.company.team.bussiness.lottery.facade;
import...;
@Component
publicclassUserCityInfoFacade{
@Autowired
privateLbsService lbsService;//外部用户城市信息RPC服务
publicMtCityInfo getMtCityInfo(LotteryContext context) {
LbsReq lbsReq = newLbsReq();
lbsReq.setLat(context.getLat());
lbsReq.setLng(context.getLng());
LbsResponse resp = lbsService.getLbsCityInfo(lbsReq);
return buildMtCifyInfo(resp);
}
privateMtCityInfo buildMtCityInfo(LbsResponse resp) {...}
}
package com.company.team.bussiness.lottery.service.impl
import...;
@Service
publicclassLotteryServiceImplimplementsLotteryService{
@Autowired
privateDrawLotteryRepository drawLotteryRepo;
@Autowired
privateUserCityInfoFacadeUserCityInfoFacade;
@Autowired
privateAwardSendService awardSendService;
@Autowired
privateAwardCounterFacade awardCounterFacade;
@Override
publicIssueResponse 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));//发出奖品实体
}
privateIssueResponse buildIssueResponse(AwardSendResponse awardSendResponse) {...}
}
package...;
import...;
@Service
publicclassLotteryApplicationService{
@Autowired
privateLotteryRiskService riskService;
@Autowired
privateLotteryConditionService conditionService;
@Autowired
privateLotteryService lotteryService;
//用户参与抽奖活动
publicResponse<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)
}
}
privatevoid validateLoginInfo(LotteryContext lotteryContext){...}
privateResponse<PrizeInfo, ErrorData> buildErrorResponse (int code, String msg){...}
privateResponse<PrizeInfo, ErrorData> buildSuccessResponse (PrizeInfo prizeInfo){...}
}
开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。