1 何为DDD
相信很多初学Java的小伙伴都会对一个名词很兴奋——微服务。没错,微服务对于很多初学者来说,显得十分高大上,且也是很多大厂面试的重点。但俗话说计算机的世界没有淫荡(银弹),微服务虽然高大上,但并不适合所有业务场景(想要了解原因的小伙伴可以自行学习,这里不做过多赘述)。
于是,在很多项目设计初期,就会有一个业界公认的,特别令人头疼的问题——该使用微服务,还是单体架构:选了单体架构的话,后期业务增长时,拆微服务工作量巨大。选了微服务的话,后期如果没有达到一定体量,那么就等于是杀坤(别给我寄绿尸体寒)用了牛刀,极大浪费了硬件资源,徒增开发成本。
那有没有什么好的解决办法呢?当然有,上述所有令人费解的问题都是存在于传统的MVC架构,关我DDD什么事?
说到DDD,那就不得不提一个人—— Eric Evans,没听说过没关系,我们暂且称之为DDD之父,这个father呢,写了一本书—— 《领域驱动设计:软件核心复杂性应对之道》 ,也就是在这本书里,第一次提出了DDD这个概念,并给出了一套十分完善的体系(后边业界绝大多数DDD架构方式都很大程度参考了书中的设计)。
那“软件核心复杂性”是什么呢? "软件核心复杂性"主要指的是那些与业务领域紧密相关的复杂性。这种复杂性通常源于业务规则的复杂性、业务逻辑的深度以及业务过程的多变性。简而言之,核心复杂性涵盖了那些难以简化的、业务本身固有的难题和挑战。 说人话就是——有些业务的复杂性是没办法通过技术手段去化简的。尤其对于敏捷式开发,需求可能一天一个变,程序员头发都快掉光了。虽然有很多好的开发原则可以缓解这些问题,比如依赖倒转——面向接口编程、高内聚低耦合、里氏替换原则等,但我只能说在复杂软件领域,只有DDD才能把这些设计模式发扬光大。
2 DDD为什么这么强
那么DDD是如何做到的呢?个人认为主要可以从以下几个方面来分析:
代码拆分依据—代码分层
-
MVC:传统的MVC模式通常是经典的三层架构,也就是我们熟知的Controller、Service、Dao(Mapper),Controller主要负责接收网络请求、将用户请求转发到Service层或进行过滤;Dao主要用于对接数据源(常见的MySQL、ES、MongoDB、Redis等),完成数据持久化,或从数据源查询数据;Service层则是主要的业务逻辑集中之地,起到一个承上启下的作用。可以看到,这种架构方式主要是从技术层面进行了代码的拆分,其优点是简单高效(至少对于项目初期来说),更适合程序员宝宝的体质。但缺点也很明显:所有业务逻辑都糅合在Service层,当项目日趋复杂,或者需求变化较频繁(这是常见的)时,不便于对某一块进行修改,容易牵一发而动全身。以电商业务为例,对User的信息管理逻辑进行修改时,常常免不了对Order相关代码进行修改。
-
DDD:而DDD则大致分为四层:API层(用户接口层)、App层(应用层)、Domain层(领域层)、Infra层(基础层)。API层大致可以理解为MVC中的Controller层;App层是对Domain层提供的领域能力进行编排,实现复杂的业务流程。注意App层只对Domain层提供的能力进行调用,而具体实现则是在Domain层。例如:
public void submitOrder(Long orderId) {
Order order = OrderFetchService.fetchById(orderId); //获取订单对象
OrderCheckSerivce.check(order); //验证订单是否有效
OrderSubmitSerivce.submit(order); //提交订单
ShoppingCartClearService.clear(order); //移除购物车中已购商品
NotifySerivce.emailNotify(order.getUser()); //发送邮件通知买家
}
OrderFetchService、OrderSubmitSerivce、ShoppingCartClearService、NotifySerivce则是对应四个不同领域提供的领域服务;Domain层则是按照领域边界将业务拆分为不同领域(可以对标微服务里的不同服务);而Infra层,通常也被称为防腐层,可以理解为对Dao层的进一步封装,这个在下边会详细介绍。可以看到,DDD主要是以业务为依据对代码进行拆分。这样的好处是对项目的“核心复杂性”进行了精细化管理,每个业务领域只需要处理好边界以内的情况(这也是微服务的核心思想之一)。即使后期业务越来越复杂,我们也可以像堆积木一样对项目进行持续迭代,不必到Service层的一团乱码里边寻找“线头”。
当然,这并不是说DDD就是绝对好的,如果项目在评估期间,就能确定其不会太复杂,且后期也不会进行过多迭代,那使用MVC,一鼓作气,迅速把项目开发出来可能更紧急。当然这样的话也就不需要用微服务了,毕竟我们这篇文章的主题就是DDD是如何做到从单体到微服务无缝切换的嘛。
对面向接口编程的贯彻—防腐层
上面提到了Infra—基础层,也叫防腐层,也是DDD的灵魂之一。那么它是用来干嘛的呢?是用来打击贪污犯罪的吗?显然不是。
在MVC模式中,我们所有业务逻辑都是跟数据库表结构高度绑定的。我们在从0到1起一个项目的时候,通常步骤是:
所有实体类属性都是与数据库字段基本对应的,Service逻辑基本都是对实体属性进行CRUD。那这哪里有问题?这么多年都是这样过来的。如果是在“完美世界”里的话,当然没有问题。
代码逻辑与数据库强绑定的结果就是:一旦表结构发生了变化,那么可能很多代码都要改。甚至在有些时候需要更换数据源,比如从MySQL查变为从ES里边查,那Service代码可能需要大改,其酸爽程度相信体验过的小伙伴都深有体会。那如何是好?计算机界有一条铁律——遇事不决加一层。上边我们提到了Infra可以理解为对Dao层的进一步封装。事实上Infra层远不止于此,字面理解:Infrastructure Layer——基础层,就是专门用来管理基础组件的。不管你是MySQL也好,ES也好,Redis也好,甚至三方接口也好。从业务上讲,我就是要根据username查用户,我才不关心你是从村口大妈那里查的,还是调FBI接口查的是吧。总的来说Infra就是对基础组件进行了统一管理,对业务屏蔽了技术的复杂性。想起了一句话——哪有什么岁月静好,不过是有人替你负重前行。还有就是DDD中的实体属性是可以不必和表字段一一对应的,数据源返回数据到业务实体之间的转换在Infra层中完成。并且DDD中的model是可以分为实体跟值对象的,这个不是本章重点,会在后边章节讲到。
3 原来是这样
综上所述,一个项目采用MVC模式跟DDD模式的包结构可能分别是这样的:
MVC
DDD
DDD模式下的包结构是不是有点眼熟?没错,这不跟微服务一样吗?只不过微服务里边的每一个服务在这个对应的是每一个包。这样的话DDD天然就带有拆分微服务所需的逻辑边界,不再需要为对公共资源进行分家而耗费大量的精力了,可以说是天生的微服务圣体啊。在DDD模式下,领域之间是存在领域边界,不能直接相互调用的,必须通过一个限界上下文(最典型的就是添加防腐层)进行通信。限界上下文只能是接口,具体的实现必须在各自领域内。这就是所谓的高内聚、低耦合。听起来是不是又有点像微服务,通常我们在使用远程调用(Dubbo、OpenFeign、gRpc、Thrift等)时都需要抽象出一个接口,Producer跟Consumer都是通过接口进行通信,实现在Producer方,我们只需要通过添加配置文件=》在Consumer通过写注解注入Producer中的实现类即可完成远程调用。(其实无论是DDD的防腐层,还是微服务的接口,都是SPI的一种形式)
那么,到现在为止,代码已经按照业务逻辑进行了拆分,并且每个领域独立运行所需的资源(Entity、Service等,在DDD中通常叫做”领域知识“)都已经包含在边界之内。从单体到微服务无非就是这个”限界上下文“的实现究竟是在本地找,还是通过注册中心到远程服务器中找而已。那么以Dubbo为例现在我们只需要在配置文件中写上相应配置。
然后再把之前的@Autowired()/@Resource()注解换成@Reference()即可,无需修改任何代码,真是!!!泰裤辣!!!
单体
微服务
4 更多精彩
综上所述,DDD真的是做到了从单体到微服务的无痛切换。这样就不必在项目初期纠结滴在鱼与熊掌之间二选一了,完全可以单体先行验证,后期根据系统负载情况决定是否拆分。当然,本文只是介绍了DDD有利于从单体切换微服务的两个必要重要的特性,DDD的含金量远不止于此。虽然文中是以MVC与DDD进行对比,但DDD不仅仅只是一种开发方式,而是涉及到软件设计初期到最终交付整套流程的一整套科学,其提倡在技术人员跟领域专家之间建立一套通用语言,并通过领域风暴事件等方法来使业务人员能够更深层次参与到软件构建过程中来,从而让软件最终能够更加符合业务需求。
这些都不是一篇两篇文章能够讲完的,我计划开设一个系列来专门分享DDD这一块内容。感兴趣的小伙伴也可以关注或在下方留言。下期准备分享一些DDD中的核心概念。
本人会在不同平台定期分享一些技术经验,如有理解错误或有表达不准确的地方,欢迎各位大佬指正
微信公众号:大大黄狗
CSDN:https://blog.csdn.net/YATaiMi
知乎:https://www.zhihu.com/people/vvguo-guo-38
稀土掘金:https://juejin.cn/user/1955357288306270