多模块与分布式
首先要明确一点,多模块和分布式是两种不同的概念,不能混淆,不然会被人笑到你35岁退休的。
多模块:顾名思义,一个整体项目拆分不同的模块,具体什么模块呢?
例如:传统SpringBoot应用从构建到Application运行,这就是个单体应用,也就是我们的pojo,controller,service以及dao是存在一个根src下,如图
大家统一以一个Maven(包管理)控制,编译会被统一打入jar/war中。
多模块实际就是单体应用的演变,说是演变,其实还够不上演变,因为演变这个词包含了创新变革,实际就是通过Maven的控制形成小小的模块管理,只能说是创新,并不能说是变革。
如图所示:
- 多模块中,我们把每个业务流程差分为一个模块,每个模块由每个模块各自的Maven进行版本控制,当前模块之前也是可以相许引用的。
- 分布式:顾名思义,相对于传统的多模块管理,分布式主要区分的是业务场景,并非的流程场景。
什么是流程场景呢?
如上图所示,controller(控制器)、service(业务处理)、DAO(数据处理)以及数据,这个就是流程,这也是我们习惯的流程体系,至于为什么是三层设计,可以百度了解一下。
什么是业务场景呢?
如上图所示,无论是C端还是B端,他们提供的都是服务,包括登录,注册,购买商品,商品页面展示等等,这些都是提供的服务,架构师我们考虑到百万级乃至千万级流量可能会拖垮单体服务,于是就把不同的业务拆分出来。部署在不同的服务器节点上,形成集群,集群之间相互通信。
当然我以上说的都很笼统,细致来说里面的技术层面有很多的应用,包括RPC通讯框架,分布式集群资源共享,熔断机制,服务治理,服务注册太多了,这就是我们面临的大环境,技术层出不穷,产品迭代快的一批。
那么我们说的多模块和分布式的区别到底是什么呢?
从业务和流程两个角度出发,从系统架构出发,分布式是实现业务服务拆分,多模块是实现流程拆分。
Maven多模块创建
上面我从个人的理解上面阐述了多模块和分布式的概念,仅仅是个人理解啊,大佬勿喷。
我是用SpringBoot创建的管理,不是用空Maven项目创建的,两者之前有什么区别呢?我的答案是有一点区别,但是不大,基本可以忽略不计。
创建SpringBoot父项目
这里没用系统镜像,我用的是阿里云镜像
https://start.aliyun.com/
groupid和artifactId都统称为“坐标”,是为了保证项目唯一性而提出的。
配置项勾选中我们什么都不需要勾选直接创建
创建完成后,红框勾选的都不需要了,因为父级项目只是一个空壳子,用于模块之前之间依赖传递和版本控制
创建子模块
创建之前我们先对系统进行模块设计
- common 公共模块:包含工具类,枚举定义,静态变量等
- config 配置模块:包括AOP切面,数据源,缓存等等一些列配置配置类容
- model 模型模块:包含POJO,VO,BO
- dao 数据层模块:包含mapper映射文件,SQL语句XML(这里我选用的MyBatis),如果选用JAP这类的ORM框架的,这个模块可以省去,但是使用自定义SQL,JAP可以选择兼容Freemaker模板引擎
- service 业务模块:包含用户业务等一些具体的业务处理
- console 控制台模块:可以理解为controller对外开放接口
- api 开放接口模块:可以理解微信QQ第三方登录接口,OAuth2.0协议规范接口
有人的问,为啥不把api 和console放在一起呢?为什么呢?不是很LOW吗?开放和不开放的接口尽量不要放在一起,在应对AOP切面权限校验的时候,这两套接口类型肯定校验的方式和机制是不一样的,这样耦合性是不是就很高了嘛,而且代码累积一个模块下,干嘛不根据业务种类的不同去区分他们呢!
新建模块
下一步
这里需要做区分了,不是随便写写的了,你瞎几把乱写,等着自己原地爆炸吧。。。。
groupid和artifactId都统称为“坐标”,既然是坐标,那就是确保他的唯一性
首先父类是com.personloger,我们这里就具体到模块com.personloger.common
artifactId就是模块的名称common
package,包路径我们为了做区分和groupid保持一致
这个模块因为我们设定只存放一些公共使用的枚举变量,公共方法工具类,因此不需要引入配置
直接创建
config 、model、service、dao,console,api 有是重复上面的创建步骤
多模块配置Maven
配置父级POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 定义SpringBoot 继承关系以及版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.personloger</groupId>
<artifactId>only-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>only-api</name>
<description>Demo project for Spring Boot</description>
<!-- 定义打包方式为pom -->
<packaging>pom</packaging>
<!-- 子模块 -->
<modules>
<module>console</module>
<module>api</module>
<module>service</module>
<module>model</module>
<module>common</module>
<module>dao</module>
<module>config</module>
</modules>
<!-- 版本控制 以及父子配置-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-boot.version>${parent.version}</spring-boot.version>
<fastjson.version>2.0.13</fastjson.version>
<mybatis.version>2.1.4</mybatis.version>
<maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
<maven.surefire.plugin.version>2.22.2</maven.surefire.plugin.version>
<hutool.version>5.8.6</hutool.version>
<springfox.swagger2.version>2.9.2</springfox.swagger2.version>
<org.redisson.version>3.16.0</org.redisson.version>
<com.google.guava.version>30.1-jre</com.google.guava.version>
<com.google.zxing.version>3.5.0</com.google.zxing.version>
<dingtalk.version>2.0.0</dingtalk.version>
<org.apache.commons.collections4.version>4.4</org.apache.commons.collections4.version>
<org.apache.commons.lang3.version>3.12.0</org.apache.commons.lang3.version>
</properties>
<dependencies>
<!-- 类工具包 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${org.apache.commons.lang3.version}</version>
</dependency>
<!-- 集合工具包 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${org.apache.commons.collections4.version}</version>
</dependency>
<!-- swagger2.0 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.swagger2.version}</version>
</dependency>
<!-- Hutool 工具集合 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- SpringBoot Base dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.personloger.OnlyApiApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
具体类容看注释
配置子模块POM
首先我们挨个打开子模块POM文件,以api为例
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 继承父POM -->
<parent>
<groupId>com.personloger</groupId>
<artifactId>only-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<!-- 定义打包方式 -->
<packaging>jar</packaging>
<groupId>com.personloger.api</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- 清空存在依赖 -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.personloger.api.ApiApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
其他的模块是这么写
删除不需要的启动项以及application.properties文件
因为我们在设计之初,只开放两个web,因此除了console和api,其他的模块下的启动项目全部删除
同时我们要删除该子模块的pom关于插件的配置
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.personloger.model.ModelApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
这个插件删除了,因为你已经没有启动项了,这个东西实际就没有存在的意义了
同时所有模块的properties文件全部删除
配置子模块相互依赖关系
上面步骤处理完成后,我们看一下配置POM文件中,我在dependencies中写了很多开源依赖,这些都是子模块需要用的,因此我在中定义统一版本,便于统一版本管理
<!-- 子模块 -->
<modules>
<module>console</module>
<module>api</module>
<module>service</module>
<module>model</module>
<module>common</module>
<module>dao</module>
<module>config</module>
</modules>
这里是子模块定义,你不定义,通过pom方式打包,就会出现依赖找不到的问题。切记,模块之间依赖并不是这个生效的,这里只是告诉maven,我打包或者编译的时候,请把这些子模块一起顺带手一起打印了。
那么子模块之间相互依赖,我们该怎么配置呢?
其实很简单,在配置子模块POM的时候,我把原有的全部清空了,一方面是告诉大家,父级POM用于公共依赖管理,子模块只需要引用需要模块即可
例如
model模块要贯穿整个项目,因此,我们在每个子模块都是引入这个依赖(自身不需要引用)
这样我们在model这个模块创建的类,在其他模块均可以使用的到,可以测试一下吆。
子模块依赖设计
- model需要配置到其他模块,自身不需要
- console和api需要service业务层处理,因此我们把service配置到console和api模块
- service业务层模块需要依赖dao模块,因为业务层下来就是持久化层
- common公共模块,因为是业务处理,所以直接给service即可
配置多环境信息
我默认是选在console模块下创建application.yml文件的,你可以选在api,或者自己新建一个web模块专门用于管控application文件,系统默认创建的是properties文件,与yml文件还有区别的,具体的区别就不介绍了,yml的树形结构看着直观一点
- application.yml
# 定义两种环境模式 dev prod
# dev 本地运行环境
# prod 线上环境,有的公司配备了流水线,自然会有测试环境
spring:
application:
name: console
profiles:
active: dev
# 对于一些公共配置我们可以配置在这里面,dev 与 prod主要是为了区分线上和线下的不同,没必要相同的配置写N遍
# 例如静态资源访问路径
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
# 模板引擎
thymeleaf:
prefix: classpath:/templates/
encoding: UTF-8
cache: false
suffix: .html
servlet:
content-type: text/html
- application-dev.yml
# 测试环境端口
server:
port: 8080
# 本地环境
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/pmsc?useUnicode=true&characterEncoding=utf8&useSLL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
hikari:
auto-commit: true
idle-timeout: 60000
connection-timeout: 60000
max-lifetime: 0
minimum-idle: 10
maximum-pool-size: 15
- application-prod.yml
# 测试环境端口
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/pms?useUnicode=true&characterEncoding=utf8&useSLL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: username
password: password
hikari:
auto-commit: true
idle-timeout: 60000
connection-timeout: 60000
max-lifetime: 0
minimum-idle: 10
maximum-pool-size: 15
我们来测试一下启动