走上DDD
还记得之前提到的代码大改版吗?说的就是从mvc架构改版成DDD架构。
代码都写完了,是什么原因让我们再次走上了重构之路呢?
一般来说,在项目初期应该选好架构,然后再进入到开发阶段,到了项目后期,如果有改动会有delay的风险,让我们再次开启重构之路的原因是
一切都缘于我们追求极致的简洁,也许我们的老毛病又犯了(白眼)。在使用mvc架构的过程中我们一直在反复的思考,主要有以下几点
- 如何让产品经理看得懂我们的代码,无论是从包的命名、还是类的命名,乃至函数的命名都能准确无误的表达想要做的事情。
- 核心业务如何摆脱对springboot框架的依赖,我们认为好的代码不应该轻易依赖第三方框架。举例,我们的service能不能不用@service标记,依然可以工作。
- 怎么样才能把代码写的更加的清晰、简洁、饱满
在这个过程中,我们重读了 《重构:改善现有代码的设计》,虽然目前代码写的应该还可以,但我们追求的是把代码写的更加的规范。
于是我们走上了领域驱动之路,在过去的经历中,我对领域驱动的落地总是望而生畏,因为确实没有优秀的项目来指导如何落地,包括Eric evans他老人家都不知道,他的回答是 By experience。
为了能够搞清楚领域驱动,我读了一些文章,另外还读了两本书
《领域驱动设计 软件核心复杂性应对之道》Eric evans
《解构领域驱动设计》张逸
DDD工程结构
接下来直接给你看一下我们整体的包结构,从包结构你能看出来领域驱动的味道
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─viens
│ │ │ │ └─award
│ │ │ │ ├─application
│ │ │ │ │ ├─controller
│ │ │ │ │ ├─convert
│ │ │ │ │ ├─request
│ │ │ │ │ └─response
│ │ │ │ │ └─mq
│ │ │ │ │ └─task
│ │ │ │ ├─domain
│ │ │ │ │ ├─model
│ │ │ │ │ ├─port
│ │ │ │ │ ├─service
│ │ │ │ │ └─setter
│ │ │ │ └─infrastructure
│ │ │ │ ├─configuration
│ │ │ │ │ ├─exception
│ │ │ │ │ ├─filter
│ │ │ │ │ ├─limiter
│ │ │ │ │ ├─message
│ │ │ │ │ │ ├─canal
│ │ │ │ │ │ └─kafka
│ │ │ │ │ ├─monitor
│ │ │ │ │ ├─scenes
│ │ │ │ │ └─springbean
│ │ │ │ └─repository
│ │ │ │ └─convert
application
该层的主要职责是协议转换和数据转换。协议转换有commandLine、mq消息协议、http协议等;数据转换主要指的是把请求实体转换成核心领域的entity。
我们在这一层做了对canal消息的监听,提供Restful API 接口。
├─application
│ ├─controller //控制器,跟mvc的控制器使用方式是一样的
│ ├─convert //对象转换
│ ├─request // 请求实体
│ └─response //响应实体
│ └─mq //消息的监听,消息转换器
│ └─task //系统执行任务,比如定时任务
domain
领域核心层,主要核心业务。比如我们奖券的分配规则等,接下来详细解释一下domain层的各个包命名,该层不依赖包括springboot的框架,那你可能有疑问,我如何把service注入到spring beans 容器中,后边我们说。
├─domain
│ ├─model // entity和值对象,怎么来区分entity和value呢,看看有没有唯一标识,如果有大概率是entity,比如person,有身份id,是实体。住址是值对象,比如住址。
│ ├─port //该包的主要作用是给核心业务提供访问第三方入口,具体是service->port
│ ├─service //具体业务实现
│ └─setter
infrastructure
基础设施层,该层主要是具体的实现方案,比如数据库操作,kafka操作,springboot框架加载等。
└─infrastructure
├─configuration
├─exception //全局异常
├─filter //过滤器
├─limiter //限流
├─message
├─canal //canal消息
└─kafka //kafka消息
├─monitor //监控
├─scenes //
└─springbean //springbean操作
└─repository //资源库
└─convert
DDD核心概念
统一语言:
在表述同一件事情的时候用相同的词汇,主要为了解决语义歧义,减少沟通成本,相关的词汇可以直接反映到代码层面,比如大家在定义商品的时候,有人说goods,有人说commodity,这样就不容易沟通。
限界上下文:
领域所处环境和边界,所处环境是指事情发生的当时情景。比如商品,在销售阶段叫商品,在运输阶段叫货品,商品就是内容,销售阶段是当时的环境
聚合根:
把不同实体以及行为聚合在一起,如果要访问聚合内的实体行为,只能通过聚合根
实体:
有唯一标识的就是实体,比如person,通过身份证来识别。当然很多时候实体会发生转变成值对象
值对象:
不能够通过唯一标识来识别,比如住址。
领域、核心域
最核心的业务是什么
支撑域
用于支撑业务的一些基础设施,可以理解成infrastructure层
通用域
通用的技术工具,比如各种utils
DDD最佳实践
eric evans提到的六边形架构
六边形架构
该图主要是domain层的一些介绍,最核心的是model,最外边是我们的port,刚才从我们的代码结构中也看到了。
整洁架构
blog.cleancoder.com/uncle-bob/2…
洋葱架构
这三种架构都是DDD实践的推荐架构,整体上都差不多,其他层都依赖于核心层,核心层不允许依赖其他层,这是核心理念。
这样做有什么好处呢?
- 可以很好的进行单元测试,核心层的自洽正好给单元测试提供了土壤
- 可以更好的保持稳定,基础层和应用层的改变,不会影响到核心层的变动。
MVC和DDD该如何选择
MVC的分层架构已经成为经典,也是我们最常用的架构模式,如此经典的架构很显然有它的用武之地。 mvc在架构层次上有简单,清晰的特点,也比较容易上手,对待一些简单的业务场景,开发效率非常好,但对于复杂的业务场景会很容易堆砌代码,形成大函数的风格,所以不是很复杂的业务场景建议用mvc。
DDD的充血模型为复杂的业务提供了良好的土壤,让复杂的操作更内聚,封装性更好,易于扩展,但对开发人员来说上手成本比较高,而且不是很容易学习。
总结
任何架构都有优点和缺点,没有任何一个是完美的,在技术选型上重要的是不追究奢华,不过分追求时尚,不过分追求华丽,而是按照自己的业务场景选择最适合自己的技术,无论是从学习成本、开发效率、质量稳定性上适合自己就好。