DDD领域驱动设计
什么是DDD
领域驱动设计(Domain-Driven Design,简称DDD):
是一种软件开发方法论,旨在帮助开发团队更好地理解和应用业务领域的知识,从而构建出更加符合业务需求的高质量软件系统。DDD 强调通过深入的领域分析和模型设计来解决复杂的业务问题,同时提倡将业务领域的知识与软件设计相结合,以实现更好的软件架构。
以下是一些关键概念和实践方法:
领域模型
领域模型是对业务领域知识的抽象和表达,它描述了业务实体、行为和关系之间的交互。领域模型是DDD 的核心,开发团队通过与领域专家合作,逐步构建出精确反映业务需求的模型。
战略设计和战术设计
DDD 将架构设计分为战略设计和战术设计两个层次。战略设计关注于整体的业务战略,包括领域边界的划分、上下文的明确和领域模型的组织。而战术设计则关注于具体的模型设计和实现,包括实体、值对象、聚合根、仓储等概念的应用。
领域驱动设计的模式
DDD 提出了一系列与领域模型相关的设计模式,例如实体(Entity)、值对象(Value Object)、聚合(Aggregate)、仓储(Repository)、领域服务(Domain Service)等。这些模式帮助开发团队更好地组织和管理领域模型,提高系统的灵活性和可维护性。
通用语言
DDD 提倡使用通用语言(Ubiquitous Language)来统一开发团队和业务专家之间的沟通。通用语言是指在业务领域中共同理解的术语和概念,开发团队应该与业务专家共同定义并沿用通用语言,以确保模型设计的准确性和一致性。
迭代开发
DDD 鼓励采用迭代、渐进的方式进行模型设计和开发,通过不断地与业务专家交流和反馈,逐步完善和优化领域模型。
DDD有什么用?什么时候要考虑用DDD?
从书名就能看到,"软件核心复杂性应对之道",这意味着DDD要处理的问题就是很复杂的,所以DDD本身,也是一种学习曲线比较陡的设计思想。关于DDD的学习和应用,对任何人都不是一个简单容易的过程。DDD中一大堆概念需要去理解,这就造成了不小的学习门槛。而在落地实践时的理解,更是仁者见仁智者见智。例如,下面大致列出了一些DDD中的基础概念:
这其中每一个概念都是行业内多年优秀开发经验的积累。要把这其中每一个概念,理解透彻并且能落地实践,都是非常费力的,尤其在使用DDD的初期,很多思维方式都需要转变。这会影响到整个开发团队,甚至连需求人员都包含其中。关于这些难点,通过实战课程加上之前推荐的资料,加上后面的实战课程,我都会通过理论+实战的方式带你去一一解决。但是,在学习之处,有一个问题需要你自己先理解,就是之前电商那么复杂的项目都开发得好好的,到了这个简单的服务开放平台,为什么要费劲巴拉的用DDD呢?
你或许应该听说过网上对于DDD的一句很经典的评价。“项目越复杂,DDD的收益越大。而项目如果很简单,DDD反而会事倍功半”。那是不是我们这个简单的服务开放平台就不应该用DDD呢?
其实,这里就首先需要你对DDD所要面对的"软件核心复杂性"有所理解。你可以先想想,要做一个软件,最复杂的地方到底是什么?是MQ、缓存、微服务这些技术吗?不是,这些技术你可能现在不会,以后学学也就会了。对于一个成熟的开发团队,这些基础技术更加不是问题。那,是庞大的业务体量,是复杂的业务流程吗?也不是。如果都是同样的CRUD,再复杂的业务流程,你一个人做不来,有一个团队来做,也就没什么了。
而如果你作为一个架构师,真正让你头疼的,往往是各种三高架构、线程安全等等这些稀奇古怪的问题。如何发现问题,如何对现有项目进行改造升级,这些才是软件所要面临的最为核心的复杂性。而这一类问题其实有一个共同点,就是变化,并且是超出你之前意料之外的变化。比如,之前的电商项目,从一开始就知道要去处理秒杀的问题,所以,从一开始,就可以引入Redis,做各种各样的并发设计。这些设计虽然复杂,你带领的是一个完整的开发团队,也就没有什么复杂的了。甚至如果你已经做过一个实现了,换成另外的场景再要实现一次,那就更容易了。而真正难以控制的是,在项目初期并没有考虑到秒杀的高并发问题,甚至都没有用上Redis,等到业务中出现了秒杀问题的时候,再要去临时变更你的项目设计,甚至可能需要重构,这时项目的质量就很难得到保证。并且,这种问题,随着你的项目规模越来越庞大,所面临的风险也会越来越大。而这种问题,就是DDD真正需要面对的软件核心复杂性问题。
因此,如果你要做的项目就是一个需求非常确定的项目,那么,不管项目多复杂,其实都没有必要非要转型成为DDD。像之前电商项目那样的短平快的设计方式会更为快捷。画清楚设计图,就可以按MVC去实现了。而如果你的项目中,有很多不确定性,以往的设计模式会遇到非常多的变数,这时DDD就是一个很好的选项了。
DDD怎么防止系统变“老”
技术的进步带来软件架构及管理方式的进步
系统怎么就“老”了?
代码层面的系统老化
传统MVC架构倾向于从数据库ER图开始进行设计。业界戏称为 Data Driven Design。
架构层面的系统老化
DDD如何防止系统老化
将真实世界与软件世界联系在一起,以领域划分代替功能模块划分。
软件发展的规律就是逐步由简单转向复杂。在简单业务下行之有效的设计方式,转到复杂业务下时,未必行之有效。就像微服务被证明并不是解决系统老化问题的万能银弹一样,DDD同样也不是。但是DDD确实给我们提供了一个新的视野,让我们在复杂业务情况下能够更好的集中精力解决业务问题,以更低的成本保障系统可持续的不断推进下去。
DDD如何指导应用架构
贫血模型 VS 充血模型
贫血模型:
public class Account{
private Long id;
private Long accountNumber;
private BigDecimal available;
public Long getId(){
return this.id;
}
public void setId(Long id){
this.id = id;
}
.....
}
充血模型:
public class Account{
private Long id;
private Long accountNumber;
private BigDecimal available;
public void withdraw(BigDecimal money){
//转入操作
available = available + money;
}
public void deposit(BigDecimal money){
//转出操作
if(available < money){
throws new InsufficientMoneyException();
}
available = available - money;
}
}
贫血模型下的业务模型变更必然带来大量代码变更:
增加计算vip等级的,实体对象到sevice方法都需改造实现
充血模型下的模型变更就变得非常简单,模型变更的影响范围也得到有效收敛。
在实体类里进行方法计算
实体(Entity)与值对象(Value Object)
实体代表那些具有唯一ID的领域对象;
值对象代表那些一成不变的、本质性的事务;
如下:地址本身属于一个实体,但在生成订单中作为订单里的一个值对象
领域服务
服务表示的就是那些在领域对象之外的操作和行为。
跨实体的业务操作,交由服务来协调。
服务用来隔离业务逻辑与技术实现。
防腐层(Anti-Conrruption Layer)
DDD如何保护领域模型
聚合(Aggregator)
实体和值对象体现的是个体的能力,聚合体现的是这些个体的系统工作能力。
聚合的作用
聚合是用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
判断聚合关系最有效的方式,就是去探讨下是否符合整体与部分的关系
聚合根(Aggregator Root)
每个聚合内部有一个外部访问聚合的唯一入口,称为聚合根。
每个聚合中应确定唯一的聚合根实体。
聚合与聚合根的作用
仓库(Repository)和工厂(Factory)
DDD的核心:领域与限界上下文
DDD中最为核心的两个概念:领域与限界上下文
在DDD领域驱动设计中,采用“分而治之”的思想,将问题域划分成一个个相对独立的子域。
通过形成统一语言在团队内共享领域的划分情况,并通过上下文地图将领域建模固定下来。
通过使用限界上下文,我们在对系统进行微服务改造时,能够相对平滑的过渡成为微服务拆分的指导方式。
领域(Domain)
关于领域,最重要的一个关键词就是范围,范围也就是边界
维护领域的方式:
一、DDD中强调通用语言:
1、通用语言是一种团队共享的语言
2、通用语言只表达一个单一的领域模型
3、DDD强调通用语言的统一表达
第二、DDD采用“分而治之”的思想,将领域不断切分成不同的子域
第三、有了领域划分后,就需要保证领域之间的边界,这个边界就是限界上下文(Bounded Context)
如何划分限界上下文
业界并无统一的方法论,可以参考“单一职责原则”,即每个限界上下文内实现的都是软件变化的同一个原因