结合案例谈谈我对领域驱动设计的理解 有更新!

  |   0 评论   |   12,196 浏览

1. 为什么要聊领域驱动设计?

领域驱动设计是一套建模的方法论。在微服务大形其道的当下,如何确保微服务拆分的结果是利大于弊的,具体到业务仍然是个难题。而领域驱动设计可以指导微服务的具体实践。

2. 设计领域模型的步骤

  1. 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
  2. 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;
  3. 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
  4. 为聚合根设计仓储,并思考实体或值对象的创建方式;
  5. 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

3. 带宽资源使用合格率

3.1 需求

CDN加速业务会在多个机房有机器,每个机房会向运营商开通带宽资源,为了监控带宽资源的使用情况,需要每周统计报表,汇报机房跑量是否合理, 以便业务进行调整。具体规则如下:
1. 带宽资源包含两个数据:接入与保底。接入是在该机房能跑的最大带宽,保底是运营商设置的一个下限值,即如果跑量带宽低于保底,那么运营商也要收保底的钱,在资源运营时必须确保跑量带宽能跑在保底之上,否则存在浪费。接入与保底每个月会调整一次。
2. 不同的机房带宽价格不一样,在业务上会尽量根据成本进行跑量带宽的分布,这里简单将机房带宽价格分为:便宜与昂贵。便宜的机房需要尽可能多跑。昂贵的机房需要尽可能少跑,但也要尽可能跑在保底之上。
3. 判断机房是否合格是根据如下规则进行划分:便宜的机房跑量在[保底, 保底*1.25]区间内;昂贵的机房跑量在[保底*1.5, 接入]

3.2 划分领域与界限上下文,以及上下文之间的关系

这个例子隐藏着两个界限上下文,一个是带宽资源使用情况的计算;还有一个不能直接看出来的是机房带宽使用数据是无法在这个系统中获取的,所以需要单独提取成一个界限上下文。

3.3 识别实体与值对象

主要由标识定义的对象被称做实体(Entity)。我的理解是,这类对象的标识特别重要,这类对象都有一个完整的生命周期。

首先我们整理下需求里涉及到的对象,如下图所示:
imagepng

首先要辨识中其中的实体,我们注意到LabRoom的标识很重要,所以肯定是实体。对于BandwidthResource、BandwidthUsage, 由于它们需要与LabRoom关联,需要当作实体来看。如果不存在对LabRoom的依赖,可以尝试将Resource与Usage改造成值对象,但在该例子中,由于转化为值对象的收益并不高,并不能减少对象的总数,所以暂时先不动。

3.4 关联聚合实体与值对象,识别聚合根

把实体与值对象分门类的聚合到实体上,这个被聚合的实体称为聚合根,通过根的访问来控制对其它对象的所有访问。只允许外部对象持有对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来个性内部对象。这种设计有利于确保聚合中的对象满足所有固定规则,也可以确保在任何状态变化时聚合作为 一个整体满足固定规则。

在这个例子里,可以将BandwidthUsage作为聚合根。在整体系统中,LabRoom是个非常重要的概念,也应该单独提取出来做为一个聚合根。

3.4.1 包的规范

参考文献里有美团的DDD工程包管理,非常不错,这里引用记录下。
1. 模块

模块(Module)是DDD中明确提到的一种控制限界上下文的手段,在我们的工程中,一般尽量用一个模块来表示一个领域的限界上下文。

如代码中所示,一般的工程中包的组织方式为{com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确的将一个上下文限定在包的内部。

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.*;//领域防腐层

对于模块内的组织结构,一般情况下我们是按照领域对象、领域服务、领域资源库、防腐层等组织方式定义的。

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.*;//领域防腐层
  1. 资源库

领域对象需要资源存储,存储的手段可以是多样化的,常见的无非是数据库,分布式缓存,本地缓存等。资源库(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;//资源库访问对象-抽奖资源库

3.4.2 代码

TODO

4. 参考资料

https://www.zhihu.com/question/56332619/answer/250971065

http://www.infoq.com/cn/articles/should-we-focus-on-ddd

https://tech.meituan.com/DDD_in_%20practice.html



---------------------------
本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处,尊重他人劳动。
转载请注明:文章转载自 xiajl.cn

评论

发表评论