记得曾经有位同事问过我这样一个问题:为什么拆分这么多模块,合并到一个模块不是更好吗?
由于这个问题时间有点久远,以前的项目模块化分我印象已经模糊,所以笔者择近弃远,以笔者最近接触的一个微服务化的项目展开分析这个问题。
这个项目的模块化分整体是这样的,一个用于约束项目所需依赖的框架统一版本号的父pom模块(应该模仿spring框架的吧,不过确实可以有,点赞),一个所有其它模块都依赖的通用代码封装模块(也就是组件模块),其它每个微服务对应一个模块。
1.parent
2.common
3.xxx-service
4.yyy-service
这样的划分看起来也算很合理,但问题就出在common
模块边界不清晰,什么都往common
里面扔。关键是其它项目也依赖这个common
模块,其它项目的代码也往这里边甩。
目前common
模块包含:
web
配置,如全局异常处理器DTO
类,如服务a
需要调用服务b
的接口,那么请求和响应类放到common
下就不需要写两次显然,这样的common
模块职责是不清晰的,臃肿的。
因此,对于本文描述的栗子,笔者会重新划分。common
应该只保留1)
和2)
,而3)
则单独写在需要的微服务模块中即可,4)
也应该抽离为多个独立的模块,5)
应该放到微服务模块下,或者与4)
同模块。
重新划分后如下:
1.parent(保留)
2.common(工具类和通用web配置)
3.xxx-service
4.yyy-service
并且,我会为每个微服务模块都拆分为两个模块(maven
项目允许一个模块下还可以有子模块)。
xxx-service
模块下的子模块为:
1.xxx-service-api
2.xxx-service-sdk
yyy-service
同理。
sdk
下面存放该微服务暴露给其它微服务调用的接口的请求与响应DTO
类,如果项目使用Open Feign
则FeignClient
接口也可以在sdk
模块下声明,类似于使用Dubbo
。
以xxx-service-sdk
为例,该模块下的包为:
command
(接收请求body
的类)dto
(响应body
类)client
(Feign
接口,或者Dubbo
接口,或者gRpc
接口)这样划分出来的包确实多了,但是却减轻了依赖负担,我们也更清楚的知道某个类应该在哪个模块下。一个模块的改动只需要通知依赖它的模块去选择更新,而不影响其它模块甚至其它项目。
为什么poi只实现excel
导入导出功能,而不在poi
中实现word
或者pdf
文件操作功能?难道是作者只懂excel
导入导出吗。
模块的划分应该是单一职责的,即使这个模块只有一个类。应将由于相同原因而修改,并且需要同时修改的东西放在一起,将由于不同原因而修改,并且不同时修改的东西分开。
正如《架构整洁之道》组件构建原则一章描述的那样:“我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。”
与SRP
(单一职责)原则中提到的“一个类不应该同时存在着多个变更原因”一样,CCP
(共同闭包)原则也认为一个组件不应该同时存在着多个变更原因。
CCP
原则实际上就是SRP
的组件版,因此单一职责也适用于组件/模块的划分。