服务端模块化架构设计|项目结构与模块化构建思路
为何要模块化
我(皱眉):这么多需求不得做一年,客户等的了么
领导(微笑):我们现在还没有客户
我(黑人问号):???没客户哪儿来的需求
领导(微笑):产品自己想的
我(沉默):。。。
领导(微笑):我们先搞个平台出来,这样比较好拉客户,balabala
痛,太痛了,搁这自娱自乐呢,这种情况要怎么设计啊
-
如果一开始设计的太完善太庞大,结果却体量不大并且只需要其中的一小部分功能,不但花费了大量的时间开发,同时可能由于需要部署大量无用但高耦合的服务而浪费服务器资源
-
如果一开始设计的比较小巧精简,结果需要支持非常庞大的体量和非常多的功能,那就需要额外的开发工作甚至重构需求和逻辑,说不定还要推到重来
于是我就在想有没有一种方式能够做到 模块化:可以根据项目的功能需求和体量进行任意模块的组合或扩展
本专栏 将通过以下几块内容来搭建一个模块化高扩展的后端服务
项目结构与模块化构建思路(本文)
未完待续......
PS:示例基于IDEA + Spring Cloud
构建工具
首先是构建工具的选择
现在主流的构建工具基本上就是Gradle
和Maven
了
一般最早开始学后端的同学都是习惯用Maven
而像我这种从Android
转后端的还是Gradle
比较好上手
构建工具的话其实两者都可以,大家可以根据自己的习惯来选择
不过我还是建议大家可以尝试一下Gradle
我后面的示例也是基于Gradle
来构建
项目结构
那我们现在就开始创建项目吧
我们要创建的项目叫juejin
,包含三个模块:juejin-user(用户)
,juejin-pin(沸点)
,juejin-message(消息)
结构1(不推荐)
Long long ago,这个世界上存在这样一种项目结构
每个模块是一个单独的项目,然后当时有10多个模块,而且还是我负责搭建的
现在想想,我真的是。。。想立马穿越回去,给自己来一个大嘴巴子
我给大家说说这种方式的优劣
缺点:
-
多模块同时打开很卡
-
每一个模块都是一个项目,模块多的情况下没办法同时打开,以一般公司的电脑配置,会很卡
-
只能要改哪个模块开哪个模块项目,有新的要开就把已经打开的关掉一个
-
有的时候刚关掉一个模块,突然想到有个地方漏改了,又把之前关掉的再打开,效率直接-50%
-
-
版本管理麻烦
-
git
管理也是多个项目,拉分支切分支也很麻烦 -
有的时候一个模块需要修改就单独拉了一个分支,最后有部分模块有某个分支,有部分模块没有
-
如果没有人专门做整理记录,这些分支大概率最后直接乱成好几副耳机线,就算有记录管理起来也是极其麻烦
-
优点:
- 无!
如果大家现在的项目是这种方式的话,emmmm,耗子尾汁吧
不过如果模块数不多,只有3-4个,这种方式也算能接受
或是需要做代码权限,如不让所有人都能看到所有代码而特意分开的话,这种是属于特殊情况了
但我还是建议大家,只要不是公司硬性规定,还是不要用这种方式来搭建
结构2(推荐)
有了前车之鉴之后,我肯定要优化一下项目的结构
我们先创建一个项目,名称叫juejin
大家可以根据自己的习惯或是公司的规定,选择Java
或Kotlin
,Gradle
或Maven
这里不用选择Spring Initializer
,因为最外面的项目只是一个目录
点击Create
之后就会生成一个项目
项目已经建好了,接下来我们来建模块
通过File => New => Module
来创建一个模块
在创建业务模块之前,我建议大家可以建一个basic
模块,用于提供全局的配置和依赖,当然名字大家可以按照自己的习惯或是经过讨论来决定,basic
只是我习惯的名称
同样的,点击Create
就会在juejin
的目录下生成一个juejin-basic
模块
用相同的操作,我们把juejin-user
,juejin-pin
,juejin-message
这几个模块也都创建出来
这里大家可以自行选择用不用Spring Initializer
- 如果不用
Spring Initializer
则需要手动修改build.gradle
补插件和依赖 - 如果使用
Spring Initializer
则需要手动删除一些无用的文件和依赖
我这里没有选择Spring Initializer
如果大家使用了Spring Initializer
可以对照着我的示例进行添加删除和修改就行了
我们的项目结构基本就是这样了,相比于之前每个模块都是一个项目的方式,这样搭建所有的模块一目了然而且方便管理
手动配置
Gradle版本
/gradle/wrapper/gradle-wrapper.properties
可以修改Gradle
的版本
不同的版本可能会导致一些兼容性问题,不过一般情况下直接用创建项目的时候自动帮你配置的版本也不会有什么问题
模块配置
settings.gradle
文件是配置模块的
大家只需要检查一下自己创建的模块是否都配置了
如果有遗漏,可以用include '{模块名称}'
添加模块
构建配置
build.gradle
文件是我们的构建配置
我们的依赖,插件都是配置在这个文件里面,等同与Maven
的pom.xml
现在我们来修改最外层的build.gradle
,就是juejin
目录下的build.gradle
//定义插件
plugins {
id 'org.springframework.boot' version '2.7.3'
id 'io.spring.dependency-management' version '1.0.13.RELEASE'
}
//应用于所有模块
allprojects {
//设置一些自定义的值
ext {
set('JuejinVersion', '1.0.0')
}
//应用插件
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
version "${JuejinVersion}"
group = 'com.bytedance.juejin'
//编译版本jdk8
sourceCompatibility = '1.8'
//仓库地址
repositories {
mavenCentral()
}
//依赖
dependencies {
if (project.name != "juejin-basic") {
implementation project(':juejin-basic')
}
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
implementation 'org.springframework.cloud:spring-cloud-loadbalancer'
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config'
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
//依赖管理
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:2021.0.4'
mavenBom 'com.alibaba.cloud:spring-cloud-alibaba-dependencies:2021.1'
}
}
//测试
test {
useJUnitPlatform()
}
//编译编码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
}
使用allprojects
可以将配置应用于所有的项目包括子模块
使用implementation project
将我们的juejin-basic
模块也依赖进来,让我们的juejin-user
,juejin-pin
,juejin-message
都可以访问到juejin-basic
中的内容
这样配置的好处就是,所有项目的依赖版本都是统一的,如果需要修改版本只要改这一个配置即可,相当于是一个全局依赖版本管理
这里有一个需要注意的点,我们看dependencies
里面的这部分内容
if (project.name != "juejin-basic") {
implementation project(':juejin-basic')
}
由于我们使用allprojects
对所有模块都进行了配置,导致juejin-basic
依赖自己本身而出现循环依赖的问题
所以我们可以在依赖juejin-basic
模块之前先做一个判断,如果模块名称不等于juejin-basic
才进行依赖
其实这也是我为什么推荐大家使用Gradle
的一部分原因,由于Gradle
本身就是脚本文件,可以方便我们在任何地方添加自己的逻辑
其他模块的build.gradle
只要按需加一些特有的依赖就行了
dependencies {
//如果有模块特有的依赖可以在这里加
}
其他的配置都可以直接删除,因为我们已经通过allprojects
全局配置了
普通微服务
当我们配置好依赖之后,只要在每个模块中添加一个启动类和配置文件,就可以完成一个普通微服务的搭建了
这里我用的Nacos
做注册中心,如果大家用的是其他的注册中心那么就按其他的注册中心进行配置就行了
其他的模块也是一样的方式添加配置即可,添加完成之后就可以直接启动运行了
到这里,一个普通的微服务就搭建好了,如果业务规划已经非常明确,那么完全可以开始下一步的开发了
模块化改造
当我们试运行之后发现,juejin-user(用户)
和juejin-message(消息)
这两个模块的体量不大,于是想要将这两个模块合并成一个服务来节省服务器资源
但是两个模块服务都有自己的启动类,让其中一个依赖另一个会有问题,所以我们需要对当前的模块服务进行一些改造
怎么改造呢?思路就是把这两个模块的启动类和配置文件去掉,然后创建一个新的模块依赖这两个模块并添加启动类了配置文件就行了
删除启动类和配置文件
首先呢,我们需要把我们模块中的启动类和配置文件都删除
添加启动模块
然后新建一个juejin-application-system(系统)
作为juejin-user(用户)
和juejin-message(消息)
的启动模块,并在build.gradle
中依赖这两个模块
接着给这个启动模块添加启动类和配置文件,这边我就略过了
之后给juejin-pin(沸点)
也添加一个启动模块juejin-application-pin
项目的结构就是这个样子了,最后启动服务
到此为止,我们已经可以根据业务需求任意的组合各个业务模块,通过添加额外的启动模块来将这些业务模块打包成一个服务运行
单体应用
如果我们把所有的模块都放在一起打包成一个服务,那就变成了一个单体应用
我们来创建一个单体应用的启动模块juejin-application-single
把配置文件,然后禁用bootstrap.yml
改成application.yml
Nacos
的注册中心和配置中心就行了
这里不用把配置文件bootstrap.yml
改成application.yml
,改成application.yml
反而可能会有报错
多项目应用
当然,如果由于公司的规定需要将每个模块都作为一个项目(类似结构1的方式),也是可以通过添加启动模块(这里应该叫启动项目)来实现模块化,只是需要额外指定每个模块(项目)的路径,思路都是一样的
总结
通过上述的这种方式,我们搭建的项目可以在需求和体量一开始不太明确的情况下有一个比较好的适配和扩展性
对于一些体量较小的模块,我们可以通过额外添加一个启动模块将其组合在一起打包成一个服务运行以节省服务器资源
对于一些体量较大的模块,我们可以单独为其添加一个启动模块做集群部署,同时也保留该模块与其他模块进行组合的一个能力
对于一些初创型公司,一方面想要快速迭代出产品,另一方面又担心后续的扩展性,可以先使用单体应用模式,之后根据各个模块的体量进行调整
对于一些新人小白,也不用再担心搭建不好项目,先基于单体应用模式开发,同时慢慢学习Spring Cloud
,说不定还能在领导面前表现一番
结束
看到这里,很多爱动脑的小伙伴肯定已经有许多问题了
比如在模块合并和分开时:
- 模块间的调用问题
- 前端路由网关的前缀问题
- 本地事务和分布式事务问题
- ...
这些问题会在本专栏后续的文章中给出一些思路和解决方案,也希望能给大家一些启发
转载自:https://juejin.cn/post/7144909909706932237