你们都知道软件开发不是一蹴而就的事情,咱们不可能在不了解产品(或行业领域)的前提下进行软件开发,在开发前一般须要进行大量的业务知识梳理,而后才能到软件设计的层面,最后才是开发。而在业务知识梳理的过程当中,必然会造成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计(DDD,Domain-Driven Design)的基本概念 。html
在业务初期,功能大都很是简单,普通的 CRUD 就基本能知足要求,此时系统是清晰的。但随着产品的不断迭代和演化,业务逻辑变得愈来愈复杂,咱们的系统也愈来愈冗杂。各个模块之间彼此关联,甚至到后期连相应的开发者都很难说清模块的具体功能和意图究竟是啥。这就会致使在想要修改一个功能时,要追溯到这个功能须要修改的点就要很长时间,更别提修改带来的不可预知的影响面。 好比下图所示:java
订单服务中提供了查询、建立订单相关的接口,也提供了订单评价、支付的接口。同时订单表是个大表,包含了很是多字段。咱们在维护代码时,将会致使牵一发而动全身,极可能本来咱们只是想改下评价相关的功能,却影响到了建立订单的核心流程。虽然咱们能够经过测试来保证功能的完备性,但当咱们在订单领域有大量需求同时并行开发时将会出现改动重叠、恶性循环、疲于奔命修改各类问题的局面,并且大量的全量回归会给测试带来不可接受的灾难。数据库
但现实中绝大部分公司都是这样一个状态,而后通常他们的解决方案是不断的重构系统,让系统的设计随着业务成长也进行不断的演进。经过重构出一些独立的类来存放某些通用的逻辑解决混乱问题,可是咱们很难给它一个业务上的含义,只能以技术纬度进行描述,那么带来的问题就是其余人接手这块代码的时候不知道这个的含义或者只能经过修改通用逻辑来达到某些需求。设计模式
实际上,领域模型自己也不是一个陌生的单词,说直白点,在早期开发中,领域模型就是数据库设计。由于你想:咱们作传统项目的流程或者说包括如今咱们作项目的流程,都是首先讨论需求,而后是数据库建模,在需求逐步肯定的过程不断的去变动数据库的设计,接着咱们在项目开发阶段,发现有些关系没有建、有些字段少了、有些表结构设计不合理,又在不断的去调整设计,最后上线。在传统项目中,数据库是整个项目的根本,数据模型出来之后后续的开发都是围绕着数据展开,而后造成以下的一个架构 :很显然,这其中存在的问题以下:微信
咱们试想一下若是一个软件产品不依赖数据库存储设备,那咱们怎么去设计这个软件呢?若是没有了数据存储,那么咱们的领域模型就得基于程序自己来设计。那这个就是 DDD 须要去考虑的问题。数据结构
当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。好比当两个对象的标识不一样时,即便两个对象的其余属性全都相同,咱们也认为他们是两个彻底不一样的实体。架构
当一个对象用于对事物进行描述而没有惟一标识时,那么它被称做值对象。由于在领域中并非任什么时候候一个事物都须要有一个惟一的标识,也就是说咱们并不关心具体是哪一个事物,只关心这个事物是什么。好比下单流程中,对于配送地址来讲,只要是地址信息相同,咱们就认为是同一个配送地址。因为不具备惟一标示,咱们也不能说"这一个"值对象或者"那一个"值对象。app
一些重要的领域行为或操做,它们不太适合建模为实体对象或者值对象,它们本质上只是一些操做,并非具体的事物,另外一方面这些操做每每又会涉及到多个领域对象的操做,它们只负责来协调这些领域对象完成操做而已,那么咱们能够归类它们为领域服务。它实现了所有业务逻辑而且经过各类校验手段保证业务的正确性。同时呢,它也能避免在应用层出现领域逻辑。理解起来,领域服务有点facade的味道。dom
聚合是经过定义领域对象之间清晰的所属关系以及边界来实现领域模型的内聚,以此来避免造成错综复杂的、难以维护的对象关系网。聚合定义了一组具备内聚关系的相关领域对象的集合,咱们能够把聚合看做是一个修改数据的单元。数据库设计
聚合根属于实体对象,它是领域对象中一个高度内聚的核心对象。(聚合根具备全局的惟一标识,而实体只有在聚合内部有惟一的本地标识,值对象没有惟一标识,不存在这个值对象或那个值对象的说法)
若一个聚合仅有一个实体,那这个实体就是聚合根;但要有多个实体,咱们就要思考聚合内哪一个对象有独立存在的意义且能够和外部领域直接进行交互。
DDD中的工厂也是一种封装思想的体现。引入工厂的缘由是:有时建立一个领域对象是一件相对比较复杂的事情,而不是简单的new操做。工厂的做用是隐藏建立对象的细节。事实上大部分状况下,领域对象的建立都不会相对太复杂,故咱们仅需使用简单的构造函数建立对象就能够。隐藏建立对象细节的好处是显而易见的,这样就能够不会让领域层的业务逻辑泄露到应用层,同时也减轻应用层负担,它只要简单调用领域工厂来建立出指望的对象就能够了。
资源仓储封装了基础设施来提供查询和持久化聚合操做。这样可以让咱们始终关注在模型层面,把对象的存储和访问都委托给资源库来完成。它不是数据库的封装,而是领域层与基础设施之间的桥梁。DDD 关心的是领域内的模型,而不是数据库的操做。
DDD 概念理解起来有点抽象, 这个有点像设计模式,感受颇有用,可是本身开发的时候又不知道怎么应用到代码里面,或者生搬硬套后本身看起来都很别扭,那么接下来咱们就以一个简单的转盘抽奖案例来分析一下 DDD 的应用。
这个系统能够划分为运营管理平台和用户使用层,运营平 台对于抽奖的配置比较复杂可是操做频率会比较低。而用 户对抽奖活动页面的使用是高频率的可是对于配置规则来 说是误感知的,根据这样的特色,咱们把抽奖平台划分针 对 C 端抽奖和 M 端抽奖两个子域。
在确认了 M 端领域和 C 端的限界上下文后,咱们再对各 自上下文内部进行限界上下文的划分,接下来以 C 端用 户为例来划分界限上下文。
首先咱们要来了解该产品的基本需求
抽奖上下文是整个领域的核心,负责处理用户抽奖的核心业务。
经过上下文划分之后,咱们还须要进一步梳理上下文之间的关系,梳理的好处在于:
在实际开发中,咱们通常会采用模块来表示一个领域的界 限上下文,好比:
对于模块内的组织结构,通常状况下咱们是按照领域对象、 领域服务、领域资源库、防腐层等组织方式定义的。
部分代码以下:
抽奖聚合根:
拥有抽奖活动id和该活动下全部可用的奖池列表,它最主要的领域功能是根据一个抽奖的场景(DrawLotteryContext),经过chooseAwardPool方法筛选出一个匹配的奖池。
package com.hafiz.business.lottery.domain.aggregate;
import ...;
public class DrawLottery {
private int lotteryId; // 抽奖id
private List<AwardPool> awardPools; // 奖池列表
public void setLotteryId(int lotteryId) {
if (lotteryId < 0) {
throw new IllegalArgumentException("非法的抽奖id");
}
this.lotteryId = lotteryId;
}
public AwardPool chooseAwardPool(DrawLotteryContext context) {
...
}
}
奖池值对象:
package com.hafiz.business.lottery.domain.valobj;
import ...;
public class AwardPool {
private String cityIds; // 奖池支持的城市
private String scores; // 奖池支持的得分
private int userGroupType; // 奖池匹配的用户类型
private List<Award> awards; // 奖池中包含的奖品
public boolean matchedCity(int cityId) {
...
}
public boolean matchedScore(int score) {
...
}
public Award randomGetAward() {
int sumOfProbablity = 0;
for (Award award : awards) {
sumOfProbablity += award.getAwardProbablity();
}
int randomNumber = ThreadLocalRandom.current().netInt(sumOfProbablity);
int range = 0;
for (Award award : awards) {
range += award.getAwardProbablity();
if (randomNumber < range) {
return award;
}
}
return null;
}
}
抽奖资源库:
咱们屏蔽对底层奖池及奖品的直接访问,仅对抽奖的聚合根资源进行管理。
package com.hafiz.business.lottery.domain.repo;
import ...;
public class DrawLotteryRepository {
@Autowried
private AwardDao awardDao;
@Autowried
private AwardPoolDao awardPoolDao;
@Autowried
private DrawLotteryCacheAccessObj drawLotteryCacheAccessObj;
public DrawLottery getDrawLotteryById(int lotteryId) {
DrawLottery drawLottery = drawLotteryCacheAccessObj.get(lotteryId);
if (drawLottery != null) {
return drawLottery;
}
drawLottery = getDrawLotteryFromDB(lotteryId);
drawLotteryCacheAccessObj.add(lotteryId, drawLottery);
return drawLottery;
}
private DrawLottery getDrawLotteryFromDB() {
...
}
}
防腐层:
以用户信息防腐层为例,它的入参是抽奖请求参数(LotteryContext),输出为城市信息(CityInfo)。
package com.hafiz.business.lottery.domain.facade;
import ...;
public class UserCityInfoFacade {
@Autowried
private CityService cityService;
public CityInfo getCityInfo (LotteryContext context) {
CityRequest request = new CityRequest();
request.setLat(context.getLat());
request.setLng(context.getLng());
CityReponse reponse = cityService.getCityInfo(request);
return buildCityInfo(reponse);
}
private CityInfo buildCityInfo(CityReponse reponse) {
...
}
}
抽奖领域服务:
package com.hafiz.business.lottery.domain.service.impl;
import ...;
@Service
public class LotteryServiceImpl implements LotteryService {
@Autowried
private DrawLotteryRepository drawLotteryRepository;
@Autowried
private UserCityInfoFacade userCityInfoFacade;
@Autowried
private AwardSenderService awardSenderService;
@Autowried
private AwardCountFacade awardCountFacade;
public LotteryReponse drawLottery(LotteryContext context) {
// 获取抽奖聚合根
DrawLottery drawLottery = drawLotteryRepository.getDrawLotteryById(context.getLotteryId());
// 增长抽奖计数信息
awardCountFacade.incrTryCount(context);
// 选中奖池
AwardPool awardPool = drawLottery.chooseAwardPool(context);
// 抽出奖品
Award award = awardPool.randomGetAward();
// 发出奖品
return buildLotteryReponse(awardSenderService.sendeAward(award, context));
}
private LotteryReponse buildLotteryReponse(AwardSendReponse awardSendReponse) {
...
}
}
用 DDD 能够很好的解决领域模型到设计模型的同步、演进最后映射到实际的代码逻辑,总的来讲,DDD 开发模式有如下几个好处 :
其实咱们能够简单认为领域驱动设计是一种指导思想,是一种软件开发方法。经过 DDD 咱们能够将系统结构设计的更加合理, 以便最终知足高内聚低耦合的要求。在个人观点来看,有点相似数据库的三范式,咱们开始在学的时候并不太理解,当有足够的设计经验之后慢慢会体会到三范式带来的好处。同时咱们也并不必定须要彻底严格按照这三范式去进行实践,有些状况下是能够也须要灵活调整的。
公众号中,咱们正在进行第一次的赠书活动,本期要赠出的三本书为:《Spring Cloud实战》、《Spring Cloud与Docker微服务架构实战》、《架构探险-轻量级微服务架构》。欢迎你们到公众号中去参与赠书活动,活动有效期三天哈~
推荐两篇不错的DDD文章:
https://www.cnblogs.com/netfocus/archive/2011/10/10/2204949.html
https://blog.csdn.net/k6T9Q8XKs6iIkZPPIFq/article/details/78909897