title: 基础微服务项目架构构建总结
date: 2022-11-16 14:08:04
tags:
1、最短路径优先
Project -> B -> C(1.0) ,Project -> C(2.0)。由于 C(2.0) 路径最短,所以项目使用的是 C(2.0)
2、POM 申明顺序优先
如果 project -> B -> C(1.0) ,project -> D -> C(2.0) 这样的路径长度一样怎么办呢?
这样的情况下,Maven 会根据 POM 文件声明的顺序加载,如果先声明了 B,后声明了 D,那就最后的依赖就会是 C(1.0)
在项目顶层的父 POM 中可以定义如下的三方 jar 的版本定义(这里的 jar 包只是定义,并没有引用,即不会下载依赖)
<dependencyManagement>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.1.43version>
dependency>
dependencyManagement>
这样需要引用这个 jar 的子模块可以忽略版本定义直接引用(此时才真正下载依赖)
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
dependencies>
随便打开一个 SpringBoot 的项目,打开 POM 文件,父级依赖都是
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>${springboot-version}version>
parent>
点击这个 parent 的依赖进去看到顶级的父级依赖为
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${springboot-version}version>
parent>
继续点进去,里面已经没有任何 jar 的实际引用了,只有各种各样的 SpringBoot 或者 Spring 生态可能会依赖到的 jar 包的版本定义
Spring 通过定义一个顶层的父级版本依赖,只要是符合 SpringBoot 大版本下的 Spring 组件内的各个 jar 版本都是统一的,如果出现依赖升级的情况,不需要再去升级一个个组件的版本,直接升级父级的依赖管理 POM 中的版本即可
参照 Spring Maven 版本管理的思路,我们也可以定义这样一个业务的顶层 Maven 版本管理工程,如 common-dependency
maven install
打包到本地仓库)即在单个业务项目上抽离出一个版本管理工程作为父工程,所有的项目都使用统一的通用的依赖版本,假如某个项目需要自定义依赖或依赖版本,在项目的顶层 POM 文件中再进行定义即可,根据依赖优先级,会优先使用项目的 POM 文件中自定义的依赖版本
1、新建一个项目
2、删掉多余的其他文件
3、进行版本管理,父依赖为 spring-boot-starter-parent
注:这里 build
里的 spring-boot-maven-plugin 插件只是为了演示同样有依赖版本继承所以才在此处定义(注释状态),该插件只需定义在项目的主启动类的 POM 文件里即可。单模块项目不影响,假如为多模块项目,打包时会报错 Unable to find main class
<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">
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.5version>
parent>
<groupId>fangroupId>
<artifactId>common-dependencyartifactId>
<version>0.0.1-SNAPSHOTversion>
<modules>
<module>demomodule>
modules>
<modelVersion>4.0.0modelVersion>
<packaging>pompackaging>
<name>common-dependencyname>
<description>common-dependencydescription>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<lombok.version>1.18.24lombok.version>
<mybatis.plus.version>3.5.1mybatis.plus.version>
<druid.version>1.2.9druid.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis.plus.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>${mybatis.plus.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>${druid.version}version>
dependency>
dependencies>
dependencyManagement>
project>
4、推送到中央仓库,本地的话 maven install 到本地仓库
5、其他项目父依赖改为 common-dependency,即可进行统一的依赖版本管理
<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">
<parent>
<groupId>fangroupId>
<artifactId>common-dependencyartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>fangroupId>
<artifactId>common-projectartifactId>
<version>0.0.1-SNAPSHOTversion>
<modelVersion>4.0.0modelVersion>
<packaging>pompackaging>
<name>common-projectname>
<description>common-projectdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
project>
依赖结构如下,不同的颜色的框表示不同的层级,或者说独立工程。如 common-dependency 为通用的顶层版本依赖工程,与 project 是独立的。project 将父依赖设置为 common-dependency,其内部模块的父依赖即相应的项目顶层 POM,module 是包含在 project 里的
单项目的话可以直接在项目顶层 POM 中进行版本控制即可,即从上图的 project 开始,或者说直接将 common-dependency 当成 project,这时 common-dependency 应该改为对应的项目名
业务模块划分没有一个严格的业界标准,也没有说一定要按照怎么设计,这里根据个人使用总结为以下几个模块,具体使用可根据情况自己进行调整:
Maven 模块 | 模块描述 | 特殊说明 |
---|---|---|
api | 将 rpc 相关的接口、所必须的交互实体、枚举等定义在此处,提供给内部其他系统进行服务调用 | 单体服务可去除此模块 |
base/comm | 与业务无关的通用配置定义在此处,如统一结果返回类、统一工具类等。具有业务无关性,与业务相关的工具类、枚举等可定义在具体的业务模块内 | |
rpc | api 包的 rpc 接口定义实现,一般来说是调用模块内的具体业务接口进行相关的处理 | 单体服务可去除此模块 |
service | 具体的服务模块,进行业务处理,不特指某一个模块名 | |
web | 在此处定义启动类,配置文件(resources 目录),配置类(RedisConfig/MyBatisConfig)等项目配置 |
依赖结构如下,在之前的基础上添加项目内部划分的模块间的依赖关系
此处同样可以将上述划分好模块的 project 抽离出来成一个 common-project 用于多项目的统一的通用配置
1、同样的新建一个项目
2、删除其他多余文件,并按照前面的模块划分创建好对应的模块
3、顶层 POM 如下,父依赖为 common-dependency,并把各个模块依赖进去
<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">
<parent>
<groupId>fangroupId>
<artifactId>common-dependencyartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modules>
<module>common-apimodule>
<module>common-basemodule>
<module>common-rpcmodule>
<module>common-servicemodule>
<module>common-webmodule>
modules>
<groupId>fangroupId>
<artifactId>common-projectartifactId>
<version>0.0.1-SNAPSHOTversion>
<modelVersion>4.0.0modelVersion>
<packaging>pompackaging>
<name>common-projectname>
<description>common-projectdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-apiartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>fangroupId>
<artifactId>common-baseartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>fangroupId>
<artifactId>common-rpcartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>fangroupId>
<artifactId>common-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>fangroupId>
<artifactId>common-webartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
dependencyManagement>
project>
4、同时根据依赖关系依赖对应的模块
如 common-api 依赖 common-base
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common-projectartifactId>
<groupId>fangroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common-apiartifactId>
<name>common-apiname>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-baseartifactId>
dependency>
dependencies>
project>
common-rpc 又依赖 common-api。由于 common-api 已经依赖了 common-base,所以不需要重复引入 common-base
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common-projectartifactId>
<groupId>fangroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common-rpcartifactId>
<name>common-rpcname>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-apiartifactId>
dependency>
dependencies>
project>
common-service 依赖 common-base
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common-projectartifactId>
<groupId>fangroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common-serviceartifactId>
<name>common-servicename>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-baseartifactId>
dependency>
dependencies>
project>
同样推送到中央仓库,本地的话 maven install 到本地仓库
目前很多业务系统都是基于 MVC 三层架构来开发的,MVC 三层架构中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:展示层、逻辑层、数据层。MVC 三层开发架构是一个比较笼统的分层方式,落实到具体的开发层面,很多项目也并不会 100% 遵从 MVC 固定的分层方式,而是会根据具体的项目需求,做适当的调整
很多 Web 或者 App 项目都是前后端分离的,后端负责暴露接口给前端调用。这种情况下,一般就将后端项目分为 Repository 层、Service 层、Controller 层。其中,Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口。这里的 Service 层假如业务复杂,可再细分为如下三层:
当然,这只是其中一种分层和命名方式。不同的项目、不同的团队,可能会对此有所调整。不过,万变不离其宗,只要是依赖数据库开发的 Web 项目,基本的分层思路都大差不差
持久化对象,通过 DAO 层向上传输的数据源对象,实体属性与表字段一一对应。简单来说 PO 就是数据库中的记录,一个 PO 的数据结构对应着库中表的结构,表中的一条记录就是一个 PO 对象,通常 PO 里面除了 getter,setter 之外没有别的方法。概念与 Entity 一致
业务对象,由 Service 层输出的封装业务逻辑的对象。BO 即 PO 的组合,如 PO1 是交易记录,PO2 是商品浏览记录,PO3 是添加购物车记录,等等组合起来形成 BO ,就是个人网站行为对象
一类业务就会对应一个 BO,数量上没有限制,而且 BO 会有很多业务操作,也就是说除了 getter,setter 方法以外,BO 会有很多针对自身数据进行计算的方法
现在很多持久层框架自身就提供了数据组合的功能,因此 BO 有可能是在业务层由业务来拼装 PO 而成,也有可能是在数据库访问层由框架直接生成
DO 主要有两种定义
数据传输对象,这个传输通常指的前后端之间的传输,Service 或 Manager 向外传输的对象
BO 和 DTO 的区别
这两个的区别主要是就是字段的删减。BO 对内,为了进行业务计算需要辅助数据,或者是一个业务有多个对外的接口,BO 可能会含有很多接口对外所不需要的数据,而 DTO 在 BO 的基础上,只要自己需要的数据,然后对外提供。在这个关系上,通常不会有数据内容的变化
现在微服务盛行,服务和服务之间调用的传输对象能叫 DTO 吗?
DTO 本身的一个隐含的意义是要能够完整的表达一个业务模块的输出,如果服务和服务之间相对独立,那就可以叫 DTO;如果服务和服务之间不独立,每个都不是一个完整的业务模块,拆开可能仅仅是因为计算复杂度或者性能的问题,那这就不能够叫做 DTO,只能是 BO
数据展示对象,通常是 Web 向模板渲染引擎层传输的对象,字段值与前端要求的字段名称保持一致。即 JSON 里的数据对象
VO 和 DTO 的区别
对于绝大部分的应用场景来说,DTO 和 VO 的属性值基本是一致的,而且它们通常都是 POJO,因此没必要多此一举,但这是实现层面的思维,对于设计层面来说,概念上还是应该存在 VO 和 DTO,因为两者有着本质的区别,DTO 代表服务层需要接收的数据和返回的数据,而 VO 代表展示层需要显示的数据
通常可能的区别如下:
比如服务层有一个 getUser()
的方法返回一个系统用户,其中有一个属性是 gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定
{"gender": "男", "age": 35}
{"gender":"帅哥", "age": "30~39"}
这时可能说,在服务层直接就返回“帅哥美女”不就行了吗?
对于大部分应用来说,这不是问题,但如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从单一职责原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的 DTO,不应该出现与表现形式的耦合
数据查询对象,各层接收上层的查询请求,超过 2 个参数的查询封装,禁止使用 Map 类来传输
示例结构图如下,个人理解可能不一样,可根据具体情况进行调整
这里由于是单体服务,去掉了 api 和 rpc 模块,假如有多个服务需要互相调用的话,加上 api 和 rpc 模块同样依赖对应的 common-api 和 common-rpc 模块即可,同时服务模块都依赖 common-service
1、按照上面的模块划分,创建好项目
2、依赖 common-project 对应的模块
如 resource_nav_comm 依赖 common-base
<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">
<parent>
<groupId>fangroupId>
<artifactId>ResourceNavigationartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>fangroupId>
<artifactId>resource_nav_commartifactId>
<version>0.0.1-SNAPSHOTversion>
<modelVersion>4.0.0modelVersion>
<name>resource_nav_commname>
<description>resource_nav_commdescription>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-baseartifactId>
dependency>
dependencies>
project>
resource_nav_web 依赖 common-web
<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">
<parent>
<groupId>fangroupId>
<artifactId>ResourceNavigationartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>resource_nav_webartifactId>
<name>resource_nav_webname>
<dependencies>
<dependency>
<groupId>fangroupId>
<artifactId>common-webartifactId>
dependency>
dependencies>
project>
resource_nav_system 模块依赖 common-service
3、然后再根据上面的模块内分层,在对应层进行相应的开发