大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
什么是领域驱动设计DDD
领域驱动设计(Domain-Driven Design,简称DDD)是由美国软件专家埃里克・埃文斯(Eric Evans)在2004年提出的软件设计方法论,旨在解决复杂软件系统开发过程中业务逻辑与技术实现之间的矛盾,提升软件系统的可维护性、可扩展性和灵活性。
说人话就是:
- What: 它是一种设计思想、一种指导原则。
- When: 设计微服务的时候,或者说,不知道怎么拆分微服务的时候。
- Why:为什么要用它,上面其实说了,不知道怎么拆分微服务的时候,可以用它来指导你如何拆分微服务。
- How:这个后面讲。
很多人都说,DDD是用来处理复杂
业务逻辑的,那多复杂
才算复杂
业务呢?
这个问题,其实和微服务什么时候用
是一个问题。
所有的技术都不是银弹
。都有适合它的使用场景。
拿微服务来说,你一个小公司,就两三个开发,硬要上微服务,拆好几个服务出来,有什么意义
吗?
是提升性能了?
是增加开发效率了?
都不是,你会发现拆分完以后,程序反而三高
了。
- 高复杂度:程序变得更加复杂了。
- 高维护成本:程序的维护成本增加了、当有需求需要修改的时候、开发效率反而降低了。
- 高运维成本:原来一台机器就满足了,你拆的服务多了,一台机器不够了。要么加机器性能要么加机器数量。
所以,适合
很重要。
俗话说的好,见人说人话,见鬼说鬼话。技术也一样。
为什么大厂都开始使用DDD了?
回到我们的问题,为什么大厂都开始使用DDD了?
因为大厂人傻钱多
?
不是,有的人会说,因为大厂的业务足够复杂
。
说对了一半。
大厂通过DDD来指导微服务的拆分,解决了复杂的业务逻辑。
这里面有一些点,我们再细细的拆分一下。
为什么要拆分微服务?
再问大家一个问题,为什么要拆分微服务?
这个问题,千人千面。没有标准答案。
但是呢,总的有一些所谓的最佳实践
。
- 当公司成长了,流量增长了,单机很难
支撑
了。
比如,你是一个做电商的公司,你某天的成交量突然飙升
。如何解决?最简单的做法,扩容机器。
如果你是单机系统,你扩容的机器其实相当于扩容了整个系统,但是,你只有某几个接口的流量很大而已,其他的接口白白浪费了机器的成本。
再比如。你家公司的流量不是突然飙升
,而是每天都很大,但是呢,仅限于交易模块。和上面的问题是一样的。
- 当公司成长了,需求变多了。
很多人开发一个项目,代码写的很乱,大家每次合并代码
都会出现一堆冲突
。
如果你拆分成微服务的话,天然的限制和约束就可以减少冲突,因为粒度
变小了。
至于为什么拆分微服务,不再赘述了。
如何拆分微服务?
先给大家看两个例子吧。
拆分方案1
小李在一家电商公司A,A公司目前的代码架构如下:
现在,A公司说,要开始拆分微服务了。小李负责拆分微服务。
小李一想,这个很简单啊,直接拆呗,一个模块一个服务就行了。交易量大只需要扩容交易服务,很完美啊。
所以,拆分完成以后,架构如下:
一开始,小李觉得挺好,但是,逐渐发现问题了。
依赖严重,一个购买接口
要跨域多个微服务。
既要从用户服务
获取用户信息,又要从商品服务
获取商品信息,还要从支付服务
进行支付,还要从库存服务
扣减库存,等等。。。
导致链路很长,接口的响应速度
反而降低了。因为网络请求太多了。
写代码的时候又发现问题了,原来呢,只需要写逻辑就行了,现在还要写RPC接口。开发效率也降低了。
最后吧,代码写完了,又发现问题了,这事务怎么处理啊,只能上分布式事务了。开发成本又上去了。
结果就是,小李被领导一顿臭骂。
小李不语,只是默默承受着。。。
拆分方案2
小李觉得诸事不顺,跳槽去了另外一家电商公司B。
B公司也要拆分微服务了,领导见小李有过拆分微服务的经验,就将这个重要的任务交给了小李。
小李:。。。
小李无奈、只好继续重操旧业。
有了上次的失败经验,小李也学聪明了。
小李接下来复盘了上次的问题:
- 微服务粒度不对
- 接口链路太长导致速度下降
- 分布式事务等导致开发效率下降,且分布式事务也导致响应时间增加。
小李痛定思痛。要解决这几个问题。
终于,小李想到了好主意。
我不按照模块拆分不就完了!!!
我按照业务拆分。
比如,购买接口
就放在交易服务里面。
那么他需要用户信息的时候自己取,不调用用户服务了,其他的逻辑也是。
这样确实解决了上面的一些问题,但是,购买
还应该放在交易服务
里面吗?
当需要增加一个接口的时候,我们如何判断它属于哪个服务呢?
小李又陷入了另外一个问题。
最后的结果,导致微服务里面代码很乱。
使用DDD拆分微服务
上面的两个案例,不知道大家遇到过没有呢?
还有很多其他的错误案例,大抵意思都差不多,就是不知道如何正确的
拆分微服务。
DDD就是干这个活的。
DDD是指导我们如何正确拆分微服务的一种方法。
DDD的一些基本概念
DDD里面有很多的概念,很难一下子说清楚,因此,这里简单介绍一下。
- 实体:使用充血模型实现的实体,既有属性、也有方法。
- 值对象:只有属性的类。
- 聚合根:一个特殊的实体,聚合的入口。
- 聚合:聚合是一个概念、也可以理解成一个模块。聚合内包含了聚合根、实体、值对象。
- 限界上下文:分割领域的边界、也是分割微服务的边界,通过这个边界明确这个接口属于哪个领域,也就是属于哪个微服务。每个领域有每个领域的上下文。
- 领域:领域也就是我们的领域模型,也可以是一个微服务。
- 子领域:一个领域可以分成多个子领域。这个就是粒度的问题了。
- 领域事件:领域之间通信的方法。通过这个来调用其他的微服务。
还有一些核心领域、支撑领域、通用领域等,都是领域的一种,作用不同而已。
一个领域
里面包含了多个子领域
,如图所示。
一个子领域
里面包含了多个聚合
,每个聚合里又有一个聚合根
作为入口,还有若干个实体
和值对象
。
通过DDD我们可以来拆分微服务。
DDD是围绕业务概念
来进行领域建模的,后续的接口也是根据业务概念来划分到对应的领域中。从而解决开发过程中,业务演进的问题。
因为当业务改变了,那么领域就改变了,对应的代码也就跟着变就好了。
所以DDD和微服务不一样,不是一种具体的架构,只是一种指导方法。
它可以划分出清晰的业务边界
也就是微服务的边界,从而让微服务的拆分更加符合业务。而不是乱拆分。
他也有一些步骤
- 战略设计:战略设计从业务的视角上看待问题,建立领域边界、划分领域、子领域等等。
- 战术设计:战术设计从技术的视角上看待问题,将领域转化成微服务,将业务实体转化成代码实体等等。
- 事件风暴:通过事件风暴,大家一起来想业务,并将业务转化成领域、子领域、领域事件、实体等等。
电子商务DDD示例
首先,需要进行事件风暴
,梳理出一些和业务逻辑有关的词汇
。
以电商举例,用户
,购买者
,订单
,商品
,库存
,收货地址
,商家
,价格
,购买行为
,下单
,支付
,取消订单
,退款
,发货
等等。
接下来进行一些识别
。
识别什么呢?识别上面的概念,也就是哪些是实体
、值对象
和领域事件
。
说的再简单一些,名词
就是实体或者值对象。如果只有属性值的就是值对象。动词
就是领域事件。
比如上面的这些里面,哪些是领域事件呢?
- 下单
- 支付
- 取消订单
- 购买
- 退款
- 发货
哪些是实体呢?
- 用户
- 购买者
- 商家
- 商品
哪些是值对象呢?
- 价格:价格仅仅是一个属性,价格变化的时候就是整个值对象进行变化。
- 收货地址:收货地址也仅仅是一个属性或者一些属性,没有方法。
- 库存:库存也同样。
有些人看到这里就会有疑问了?用户和购买者不是一个东西吗?
这里就需要提到另外一个概念了。限界上下文
。
这个在上面简单介绍过。这里再说一下。
这个概念有两个意思
- 边界:用来划分领域的边界,也就是微服务的边界。
- 上下文:在划分的边界之内,有着上下文。同样的一个东西,在不同领域里面,也就是在不同的上下文环境中,意思是不一样的。
比如笨蛋
这个词吧。
在小情侣你侬我侬之间说,哎呀,你个笨蛋
。就是打情骂俏的意思。当然不是说你真的笨了。
那换一个环境呢,你在学习的时候,老师跟你说:你真是个笨蛋!
。这里就是真的在说你笨了。
所以呢,不同的环境,不同的上下文里面,一个词语的意思是不一样的。
这里也是,都是用户,但是在浏览商品的时候他只是用户
。但是在购买的时候,他就是购买者
了。
这样,他就在两个领域里面存在了。在用户领域
里面是用户实体,在交易领域
里面是购买者实体。
接下来可以进行聚合
操作,也就是将意思相近、内容相近的放到一起,放到一个聚合里面。再根据聚合的内容划分出领域、子领域等。
比如
- 用户聚合:里面就包含了用户实体。
- 商品聚合:里面包含了商品实体、价格值对象、库存值对象。
- 订单聚合:里面包含了订单实体、购买者实体、收货地址值对象。
。。。
每个聚合里面还要有一个聚合根
作为聚合的入口。
接下来将聚合划分到领域中就可以了。
比如
- 用户领域:包含了用户聚合
- 交易领域:包含了商品聚合和订单聚合。也可以划分成两个子领域,一个子领域包含一个聚合。
。。。
还有一些领域事件。作为通信。
比如
- 下单领域事件:用户聚合发起事件 -》 订单聚合接受事件。完成下单操作。
接下来我们需要把上面梳理出来的内容映射
到代码上。
比如,我们将上面的内容放到代码里面。这里面有两种方式,聚合是DDD中的最小单元
,所以可以把一个聚合部署为一个微服务。
当然了,也可以一个领域作为一个微服务,具体的情况,根据自身业务和流量这些具体考虑就可以了。
我们这里以领域做为微服务来示例:
- 用户微服务
- 交易微服务
先看用户微服务的文件夹吧,因为分层放在了下面介绍,所以这里的代码我们仅展示领域层
的代码。
- userAgg: userAgg文件夹,代表了用户聚合。
- core: 聚合的核心代码,包含了聚合根、实体、值对象。一个文件夹。
- userAggRoot.java: 用户聚合根实体,是一个java文件,一个class类。
- event: 聚合的一些领域事件代码。
- buyEvent.java: 下单的领域事件。
- core: 聚合的核心代码,包含了聚合根、实体、值对象。一个文件夹。
这个实体采用的是充血模型
。
简单看一下代码:
public class userAggRoot {
private Integer userId; //userId是这个实体中的一个值对象。
private String name; //name也是一个值对象。
public UserAggRoot getUserInfo(Integer userId) {
// 获取用户实体的信息
}
public void updateUserName(String name) {
// 更新用户名称
}
//getter和setter....
}
领域事件的代码,其实简单来说,因为跨越微服务了,所以可以直接通过消息队列来进行事件通信。
public class buyEvent {
public void buy() {
// 组装事件信息并且发送事件
}
}
再看一下交易微服务:
- shopAgg: 代表了商品聚合,是一个文件夹
- core:
- shopAggRoot.java: 商品聚合根实体。
- priceVO.java: 价格值对象的类,一个class。VO代表的是Value Object的意思。
- inventoryVO.java: 库存值对象的类,一个class。
- core:
- orderAgg: 代表了订单聚合,是一个文件夹
- core:
- orderAggRoot.java: 订单聚合根实体。
- buyerEntity.java: 购买者实体,一个java的class。
- addressVO.java: 收货地址值对象。
- event:
- orderEvent: 订单事件,发送订单事件可以给物流微服务,进行物流发货。还有积分服务进行积分处理等等。。。
- core:
代码类似:
public class shopAggRoot {
private Integer shopId; // 商品id值对象
private String shopName; //商品名称值对象
private PriceVO price; //价格值对象
private InventoryVO inventory; //库存值对象
public void subInventory() {
// 扣减库存
// 1. 创建新的库存值对象
// 2. 替换inventory属性
// 。。。。
}
}
看一些值对象:
public class priceVO{
private BigDecimal originalPrice; //原价格
private BigDecimal currentPrice; //当前价格,可能是优惠后的价格等等
}
public class inventoryVO{
private Long inventory; //库存
}
DDD的分层架构
DDD的分层和传统的MVC分层不太一样。
传统的后端服务一般是这样的层次
- controller:入口层
- service:业务逻辑层
- dao:数据层
DDD则是分成了下面四层
- 用户接口层:也就是传统的入口层
- 应用层:应用层调用领域层的聚合来完成操作,进行服务编排。可以调用多个聚合来共同完成操作。但是应用层和传统的业务逻辑层不同,它不负责完成业务逻辑,仅仅是服务编排,具体的业务逻辑由领域层实现。
- 领域层:领域层是核心,包含了聚合、实体、值对象和聚合根。每个实体都是充血模型,包含了逻辑操作。
- 基础层:基础层提供了基础服务,比如缓存、队列、数据库等等。
总结
我们简单的走了一遍DDD进行领域拆分。也让大家明白了为什么大厂会采用DDD。
因为大厂的微服务架构很完善了,他们的微服务复杂,使用DDD来指导微服务的拆分是一种有效的方法。
可以让代码和业务契合,当业务改变的时候代码随之改变。