什么是Evans DDD
2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计 )简称Evans DDD。
领域驱动设计事实上是针对OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术架构进行了分层规划,同时对每个类进行了策略和类型的划分。
领域模型是领域驱动的核心。采用DDD的设计思想,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射。领域模型就是由这样许多的细粒度的类组成。基于领域驱动的设计,保证了系统的可维护性、扩展性和复用性,在处理复杂业务逻辑方面有着先天的优势。
分析设计发展的三个阶段
第一阶段:围绕数据库的驱动设计,新项目总是从设计数据库及其字段开始。
过去软件系统分析设计总是从数据库开始,这种围绕数据库分析设计的缺点非常明显:
1.分析方面:不能迅速有效全面分析需求。
2.设计方面:导致过程化设计编程,丧失了面向对象设计的优点。
3.运行方面:导致软件运行时负载集中在数据库端,系统性能难于扩展,闲置了中间件J2EE服务器处理性能。
对象和关系数据库存在阻抗,本身是矛盾竞争的。
第二阶段:面向对象的分析设计方法诞生后,有了专门的分析和设计阶段之分,但是分析阶段和设计阶段是断裂的。
第二阶段比第一阶段进步很多,开始采取面向对象的方法来分析设计需求。
分析人员的职责:是负责从需求领域中收集基本概念。面向需求。
设计人员的职责:必须指明一组能北项目中适应编程工具构造的组件,这些组件必须能够在目标环境中有效执行,并能够正确解决应用程序出现的问题
第三阶段:融合了分析阶段和设计阶段的领域驱动设计(Evans: DDD)。
分析模型会有知识不断消化的过程,但在编码时这些知识会被遗弃。
开发人员被迫为设计进行新的抽象,那么分析人员嵌入模型中知识不能保留和重新发现。
分析模型很多重要发现更改往往出现在设计实现过程,预先的模型可能深入研究无关主题,忽视重要主题。
什么是领域模型 Domain Model:
在软件工程中,领域模型是包含属性和行为的业务模型。
领域模型只有业务(真实世界的概念),没有技术。只表达需求真实世界模型,和软件架构技术无关。在项目中,领域层为整个项目的核心,其他的都作为辅助层。
举一个机器人的例子:
机器人的领域模型:
分层领域:
将领域概念和其他软件技术相关概念分离,可以避免混淆、在系统庞大中失去对领域的把握。领域驱动设计的分层架构和构成要素,这部分内容在Eric Evans的书中有非常详尽的描述:
表现层:负责显示,解析用户命令
应用层:指挥领域对象实现功能,必须保持简练,不是核心。
领域层:核心 业务概念
基础层:与软件技术相关。
拿一个航运系统举例子:
项目的分层逻辑:
表现层:接受用户输入(屏幕绘制 )
向用户显示信息 (显示结果)
领域层: 执行业务逻辑 (查询所有城市 城市和货物关联)
基础层: 访问数据库(查询数据库 提交数据库更改网络通信)
不要将UI 数据库和其他支持代码直接写到业务对象中,虽然简单容易,但是拓展困难,难于自动化测试。
分层的优点:
每个层都是内聚的,并且只依赖它的下层,为了实现各层的最大解耦,IOC/DI容器是当前Java业务层的最好选择 。
将所有与需求领域有关的代码集中在领域层,并与用户UI层 应用层和基础层分离。领域对象就可以重点关注表达领域模型,不关心自己的显示存储和管理应用任务等事情。
适合分布式部署,提升性能,高可伸缩性。
领域模型的切割:
领域驱动设计除了对系统架构进行了分层描述,还对对象(Object)做了明确的职责和策略划分:
1、实体(Entities):具备唯一ID,能够被持久化,具备业务逻辑,对应现实世界业务对象。
2、值对象(Value objects):不具有唯一ID,由对象的属性描述,一般为内存中的临时对象,可以用来传递参数或对实体进行补充描述。
3、工厂(Factories):主要用来创建实体,目前架构实践中一般采用IOC容器来实现工厂的功能。
4、仓库(Repositories):用来管理实体的集合,封装持久化框架。
5、服务(Services):为上层建筑提供可操作的接口,负责对领域对象进行调度和封装,同时可以对外提供各种形式的服务。
领域驱动设计实践:
基于分布式的DDD项目分层格式:
如果开发者是用maven做的项目管理,给大家推荐一个十分实用的多模块项目结构,模块的分层和依赖关系如下:
基于rpc的DDD项目分层格式:
我们用这个分层格式实现一个token获取与token验证的服务,我们可以将token作为一个领域。
TokenModel 作为实体对象
TokenRule作为值对象
将TokenModel 中的属性与行为合并为TokenDomain作为领域对象
项目结构:
应用层 Controller代码:
@Controller
@RequestMapping("/token")
public class TokenController {
@Autowired
private TokenService tokenService;
private static final Gson gson = new Gson();
@RequestMapping("/apply/{bizCode}/{bizId}")
@ResponseBody
public String apply(@PathVariable String bizCode, @PathVariable String bizId){
TokenCodeRule rule = new TokenCodeRule();
rule.setBizCode(bizCode);
rule.setBizId(bizId);
rule.setTimeout(100);
TokenModel tokenModel = tokenService.applyCode(rule);
return gson.toJson(tokenModel);
}
@RequestMapping("/check/{bizCode}/{bizId}")
@ResponseBody
public boolean check(@PathVariable String bizCode, @PathVariable String bizId){
TokenCodeRule rule = new TokenCodeRule();
rule.setBizCode(bizCode);
rule.setBizId(bizId);
return tokenService.checkCode(rule);
}
}
service:
@Service("tokenService")
public class TokenServiceImpl implements TokenService {
@Autowired
private TokenDomainRepository tokenDomainRepository;
@Override
public TokenModel applyCode(TokenCodeRule rule) {
TokenDomain domain = tokenDomainRepository.findTokenDomain(rule.getBizCode(), rule.getBizId());
if(null != domain){
// 存在库中,但已经无效或者已经过期,可以使用,更改其状态以及相关信息
domain.getTokenCode().setBizCode(rule.getBizCode());
domain.getTokenCode().setBizId(rule.getBizId());
domain.getTokenCode().setGeneratedDt(System.currentTimeMillis());
domain.getTokenCode().setTimeout(rule.getTimeout());
domain.update();
}else {
// 没有存在,可以使用,并存入库中
domain = tokenDomainRepository.createTokenDomain();
domain.getTokenCode().setBizCode(rule.getBizCode());
domain.getTokenCode().setBizId(rule.getBizId());
domain.getTokenCode().setGeneratedDt(System.currentTimeMillis());
domain.getTokenCode().setTimeout(rule.getTimeout());
domain.store();
}
return domain.getTokenCode();
}
@Override
public boolean checkCode(TokenCodeRule rule) {
TokenDomain domain = tokenDomainRepository.findTokenDomain(rule.getBizCode(), rule.getBizId());
return domain.isAvaliable();
}
}
@Service("tokenDomainRepository")
public class TokenDomainRepository {
@Autowired
private TokenCodeDao tokenCodeDao;
public TokenDomain createTokenDomain() {
TokenDomain domain = new TokenDomain(this);
return domain;
}
public TokenDomain findTokenDomain(String pk){
TokenModel tokenModel = this.find(pk);
if(null == tokenModel){
return null;
}
TokenDomain domain = new TokenDomain(this);
domain.fillDomain(tokenModel);
return domain;
}
public TokenDomain findTokenDomain(String bizCode, String bizId){
return findTokenDomain(this.generatePK(bizCode, bizId));
}
private String generatePK(String bizCode, String bizId) {
if (StringUtils.isBlank(bizCode) || StringUtils.isBlank(bizId)) {
return StringUtils.EMPTY;
}
return new TokenModel(bizCode, bizId).getPk();
}
public TokenModel find(String pk){
return tokenCodeDao.find(pk);
}
public boolean insert(TokenModel tokenModel){
return tokenCodeDao.insertOrUpdate(tokenModel);
}
public boolean update(TokenModel tokenModel){
return tokenCodeDao.insertOrUpdate(tokenModel);
}
public boolean remove(String pk){
return tokenCodeDao.remove(pk);
}
}
public class TokenModel {
/**
* 存储主键
*/
protected String pk;
/**
* Token属于哪个业务或者商户Code
*/
private String bizCode;
/**
* Token所属子业务ID[比如:用户ID]
*/
private String bizId;
/**
* Token生成时间
*/
protected long generatedDt;
/**
* Token过期时间(单位:秒)
*/
protected int timeout;
public TokenModel() {
}
public TokenModel(String bizCode, String bizId) {
this.bizCode = bizCode;
this.bizId = bizId;
this.generatedDt = System.currentTimeMillis();
this.timeout = 1000;
}
public String getPk() {
if (StringUtils.isNotBlank(this.pk)) {
return this.pk;
} else if (StringUtils.isNotBlank(this.bizCode) && StringUtils.isNotBlank(this.bizId)) {
this.pk = DigestUtils.digest(new StringBuffer(this.bizCode).append("_").append(this.bizId).toString());
return this.pk;
}
return StringUtils.EMPTY;
}
public void setPk(String pk) {
this.pk = pk;
}
public long getGeneratedDt() {
return generatedDt;
}
public void setGeneratedDt(long generatedDt) {
this.generatedDt = generatedDt;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public String getBizCode() {
return bizCode;
}
public void setBizCode(String bizCode) {
this.bizCode = bizCode;
}
public String getBizId() {
return bizId;
}
public void setBizId(String bizId) {
this.bizId = bizId;
}
}
public class TokenDomain {
private TokenDomainRepository tokenDomainRepository;
protected TokenModel tokenCode = new TokenModel();
public void fillDomain(TokenModel tokenCode) {
this.tokenCode = tokenCode;
}
public TokenModel getTokenCode() {
return tokenCode;
}
public TokenDomain(TokenDomainRepository tokenDomainRepository) {
this.tokenDomainRepository = tokenDomainRepository;
}
public boolean isAvaliable(){
return this.tokenCode.getGeneratedDt() + 1000 * this.tokenCode.getTimeout() > System.currentTimeMillis();
}
/**
* 插入一条数据
*/
public void store() {
this.tokenDomainRepository.insert(this.tokenCode);
}
/**
* 更新一条数据
*/
public void update() {
this.tokenDomainRepository.update(this.tokenCode);
}
/**
* 清除记录
*/
public void remove() {
this.tokenDomainRepository.remove(this.tokenCode.getPk());
}
}
基础设施层:
@Component("tokenCodeDao")
public class TokenCodeDaoImpl implements TokenCodeDao{
private static final Map dbMock = new ConcurrentHashMap();
@Override
public TokenModel find(String pk) {
return dbMock.get(pk);
}
@Override
public boolean insertOrUpdate(TokenModel tokenModel) {
dbMock.put(tokenModel.getPk(), tokenModel);
return true;
}
@Override
public boolean remove(String pk) {
dbMock.remove(pk);
return true;
}
}